JustToThePoint English Website Version
JustToThePoint en español

Arch Linux Post-Installation: Dotfiles Management and Zsh Configuration

The excitement of learning separates youth from old age. As long as you’re learning, you’re not old, Rosalyn S.Yalow

image info

Introduction

With the core system utilities and development tools installed, we now focus on personalizing the environment and ensuring configuration portability across machines. This section covers two critical post-installation scripts: 11-dotfiles.sh and 12-zsh.sh.

First, we implement a robust dotfiles management strategy. Instead of editing configuration files scattered across the home directory, script 11 centralizes them in a version-controlled repository. It uses symbolic links to connect these files to their expected system locations —creating a “single source of truth” where updates in the repository are immediately reflected in the active configuration for tools like Neovim, Rofi, and VS Code.

Next, we upgrade the shell experience. Script 12 installs Zsh and Oh My Zsh, transforming the standard terminal into a powerful interface. It installs essential plugins for autosuggestions and syntax highlighting, and integrates Starship, a fast, cross-shell prompt that provides immediate visual context (such as Git status and active Python versions). Together, these scripts bridge the gap between a raw installation and a fully personalized, productive workstation.

#!/usr/bin/env bash
# ── 11-dotfiles.sh ────────────────────────────────────────────────────────────
# Clone the dotfiles repository and create symlinks for configuration files.
# This script assumes you have already installed the necessary tools (e.g., Zsh,
# Neovim, Rofi, VS Code, Alacritty, Fastfetch, Starship) via other scripts.
#
# Symlink strategy:
#   All config files live in ~/dotfiles/ (version controlled).
#   Symlinks point from ~/.config/ or ~/ to the dotfiles.
#   One source of truth: edit dotfiles/ and the live config changes instantly.
#
# Run: bash 11-dotfiles.sh
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail

DOTFILES="$HOME/dotfiles"
DOTFILES_REPO="https://github.com/nmaximo7/dotfiles"   # Update with your repo URL

# ── Helper ────────────────────────────────────────────────────────────────────
link() {
  local src="$1"
  local tgt="$2"
  # Source must exist
  if [[ ! -e "$src" ]]; then
    echo "  ⚠️  Source not found, skipping: $src"
    return 0
  fi
  # Ensure parent directory exists
  mkdir -p "$(dirname "$tgt")"

  # If target exists, handle backup
  if [[ -e "$tgt" || -L "$tgt" ]]; then
    echo "  📦  Backing up: $tgt → ${tgt}.bak"
    mv "$tgt" "${tgt}.bak"
  fi

  # Create the symlink
  ln -s "$src" "$tgt"
  echo "  🔗  $(basename "$tgt") → $src"
}

# ── Clone dotfiles ────────────────────────────────────────────────────────────
if [[ -d "$DOTFILES/.git" ]]; then
  echo "── Dotfiles already cloned at $DOTFILES — pulling latest..."
  git -C "$DOTFILES" pull --ff-only
else
  echo "── Cloning dotfiles from $DOTFILES_REPO..."
  git clone "$DOTFILES_REPO" "$DOTFILES"
fi

# ── Make scripts executable ───────────────────────────────────────────────────
find "$DOTFILES/scripts" -name "*.sh" -exec chmod +x {} \; 2>/dev/null || true
find "$DOTFILES/scripts" -name "*.py" -exec chmod +x {} \; 2>/dev/null || true
echo "  ✅  scripts/ made executable"

# ── Zsh ───────────────────────────────────────────────────────────────────────
echo "── Linking Zsh config..."
link "$DOTFILES/myArch/config/zsh/.zshrc"            "$HOME/.zshrc"
link "$DOTFILES/myArch/config/zsh/.zsh_aliases"       "$HOME/.zsh_aliases"
# Fastfetch
link "$DOTFILES/myArch/config/fastfetch/config.jsonc" \
     "$HOME/.config/fastfetch/config.jsonc"

# Starship
link "$DOTFILES/myArch/config/starship/starship.toml" \
     "$HOME/.config/starship.toml"

# Set Zsh as the default shell
if [[ "$SHELL" != "$(which zsh)" ]]; then
  echo "  Setting Zsh as default shell..."
  chsh -s "$(which zsh)"
  echo "  ✅  Default shell changed to Zsh (takes effect after next login)"
else
  echo "  ✅  Zsh is already the default shell"
fi

# ── Neovim ────────────────────────────────────────────────────────────────────
# Neovim reads from ~/.config/nvim/
# We symlink the entire nvim/ directory from dotfiles.
echo "── Linking Neovim config..."
link "$DOTFILES/myArch/config/nvim" "$HOME/.config/nvim"

# Install plugins on first run (assumes lazy.nvim or packer as plugin manager)
# Run headless so it doesn't open a window. This is a fallback chain for automatically installing Neovim plugins
echo "  Installing Neovim plugins (headless)..."
# a) Try Lazy.nvim plugin sync: Runs Neovim without UI (--headless).
# b) "+Lazy! sync" executes the command :Lazy! sync inside Neovim (This installs/updates plugins using Lazy.nvim).
# c) After the command finishes, quit Neovim (:qa).
# d) Suppresses error messages (stderr), 2>/dev/null
nvim --headless "+Lazy! sync" +qa 2>/dev/null || \
# If Lazy.nvim isn’t installed, this command will fail, so the script tries
# "+PackerSync" is the equivalent plugin‑sync command for Packer.nvim
# Same structure: run headless, installs/updates plugins, quit Neovim, and suppresses error messages.
nvim --headless "+PackerSync" +qa 2>/dev/null || \
# If both fail, print a warning
echo "  ⚠️  Could not auto-install plugins — open nvim and run :Lazy sync manually"

# ── Rofi ──────────────────────────────────────────────────────────────────────
# Rofi launcher + theme.
# On Arch/Niri you can still use rofi (XWayland) or rofi-wayland (native).
echo "── Installing and linking Rofi..."
paru -S --needed --noconfirm rofi-wayland

link "$DOTFILES/myArch/config/rofi/rounded-dark.rasi"  "$HOME/.config/rofi/rounded-dark.rasi"
link "$DOTFILES/myArch/config/rofi/rounded-common.rasi"  "$HOME/.config/rofi/rounded-common.rasi"
echo "  ✅  Rofi configured"

# ── VS Code ───────────────────────────────────────────────────────────────────
# VS Code user config lives in ~/.config/Code/User/ on Linux.
echo "── Linking VS Code config..."
mkdir -p "$HOME/.config/Code/User"
mkdir -p "$HOME/.config/Code/User/snippets"

link "$DOTFILES/myArch/config/vscode/settings.json"     "$HOME/.config/Code/User/settings.json"
link "$DOTFILES/myArch/config/vscode/markdown.json"  "$HOME/.config/Code/User/snippets/markdown.json"
echo "  ✅  VS Code config linked"

# ── Alarcritty ───────────────────────────────────────────────────────────────────
echo "── Linking Alacritty config..."
link "$DOTFILES/myArch/config/alacritty/alacritty.toml" \
     "$HOME/.config/alacritty/alacritty.toml"
link "$DOTFILES/myArch/config/alacritty/dracula.toml" \
     "$HOME/.config/alacritty/dracula.toml"

# ── Summary ───────────────────────────────────────────────────────────────────
echo
echo "── Symlink summary:"
# This simply iterates over a hard‑coded list of paths.
for path in \
  "$HOME/.zshrc" \
  "$HOME/.zsh_aliases" \
  "$HOME/.config/fastfetch/config.jsonc" \
  "$HOME/.config/starship.toml" \
  "$HOME/.config/nvim" \
  "$HOME/.config/alacritty/alacritty.toml" \
  "$HOME/.config/alacritty/dracula.toml" \
  "$HOME/.config/rofi/rounded-dark.rasi" \
  "$HOME/.config/rofi/rounded-common.rasi" \
  "$HOME/.config/Code/User/settings.json" \
  "$HOME/.config/Code/User/snippets/markdown.json"
do
  if [[ -L "$path" ]]; then
  # If the path is a symlink (-L checks specifically for symbolic links)
    echo "  ✅  $path → $(readlink "$path")"
  elif [[ -e "$path" ]]; then
  # Else if the path exists but is not a symlink; -e means “exists (file, directory, or symlink)"
    echo "  ⚠️  $path exists but is NOT a symlink"
  else
  # Else: the path doesn’t exist
    echo "  ❌  $path — source not found in dotfiles"
  fi
done

echo
echo "✅  11-dotfiles: done"
#!/usr/bin/env bash
# ── 12-zsh.sh ─────────────────────────────────────────────────────────────────
# Install and configure Zsh with Oh My Zsh, plugins, and Starship prompt.
# Designed for **Arch Linux** only.
# What this script installs:
#   1. Zsh                    — the shell itself
#   2. Oh My Zsh              — plugin/theme framework
#   3. zsh-autosuggestions    — grey completion suggestions as you type
#   4. zsh-syntax-highlighting — live syntax colouring as you type
#   5. Starship               — cross-shell prompt (replaces ZSH_THEME)
#   6. bash-insulter          — insults you on wrong commands
#   7. Symlinks               — .zshrc and .zsh_aliases from dotfiles
#   8. Default shell change   — sets Zsh as your login shell
#
# Must run AFTER 11-dotfiles.sh (needs ~/dotfiles/zsh/ to exist).
# Run: bash 12-zsh.sh
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail

DOTFILES="$HOME/dotfiles"

# ── 1. Ensure Zsh is installed ────────────────────────────────────────────────
if ! command -v zsh &>/dev/null; then
  echo "── Installing Zsh..."
  # Arch
  sudo pacman -S --needed --noconfirm zsh 2>/dev/null || \
  # Ubuntu / Debian
  sudo apt-get install -y zsh 2>/dev/null || \
  echo "  ⚠️  Could not auto-install zsh — install it manually first"
fi
echo "  ✅  Zsh: $(zsh --version)"

# ── 2. Oh My Zsh ──────────────────────────────────────────────────────────────
# Oh My Zsh manages plugins and themes.
# RUNZSH=no   — don't launch zsh immediately after install (we're in a script)
# CHSH=no     — don't change the default shell here; we do it explicitly below
if [[ -d "$HOME/.oh-my-zsh" ]]; then
  echo "  ✅  Oh My Zsh already installed — updating..."
  git -C "$HOME/.oh-my-zsh" pull --ff-only 2>/dev/null || true
else
  echo "── Installing Oh My Zsh..."
  RUNZSH=no CHSH=no \
    sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
  echo "  ✅  Oh My Zsh installed"
fi

# ── 3. Oh My Zsh custom plugins ───────────────────────────────────────────────
ZSH_CUSTOM="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"

# zsh-autosuggestions: shows ghost-text completions based on history.
# As you type, it suggests the most recent matching command in grey.
# Accept with → (right arrow) or End key.
AUTOSUGGEST_DIR="$ZSH_CUSTOM/plugins/zsh-autosuggestions"
if [[ ! -d "$AUTOSUGGEST_DIR" ]]; then
  echo "── Installing zsh-autosuggestions..."
  git clone --depth 1 \
    https://github.com/zsh-users/zsh-autosuggestions \
    "$AUTOSUGGEST_DIR"
  echo "  ✅  zsh-autosuggestions installed"
else
  echo "  ✅  zsh-autosuggestions already installed"
  git -C "$AUTOSUGGEST_DIR" pull --ff-only 2>/dev/null || true
fi

# zsh-syntax-highlighting: colours your command line as you type.
# Valid commands appear green, invalid commands appear red, strings in yellow.
# Must be the LAST plugin in the plugins=() list in .zshrc.
HIGHLIGHT_DIR="$ZSH_CUSTOM/plugins/zsh-syntax-highlighting"
if [[ ! -d "$HIGHLIGHT_DIR" ]]; then
  echo "── Installing zsh-syntax-highlighting..."
  git clone --depth 1 \
    https://github.com/zsh-users/zsh-syntax-highlighting.git \
    "$HIGHLIGHT_DIR"
  echo "  ✅  zsh-syntax-highlighting installed"
else
  echo "  ✅  zsh-syntax-highlighting already installed"
  git -C "$HIGHLIGHT_DIR" pull --ff-only 2>/dev/null || true
fi

# ── 4. Starship prompt ────────────────────────────────────────────────────────
# Starship is a fast, highly customisable cross-shell prompt written in Rust.
# It shows git branch, Python venv, Node version, exit code, and much more.
# It replaces ZSH_THEME in .zshrc — both can coexist but starship wins.
if command -v starship &>/dev/null; then
  echo "  ✅  Starship already installed: $(starship --version | head -1)"
else
  echo "── Installing Starship..."
  curl -sS https://starship.rs/install.sh | sh -s -- --yes
  # Downloads the official install.sh script from https://starship.rs
  # Installs the correct Starship binary and skips the “Do you want to continue?” prompt because of --yes
  echo "  ✅  Starship installed: $(starship --version | head -1)"
fi

# Configure Starship with nerd‑font symbols (assumes Hack Nerd Font is already installed from 01-base.sh)
mkdir -p "$HOME/.config"
if [[ ! -f "$HOME/.config/starship.toml" ]]; then
  echo "  Applying Starship nerd-font-symbols preset..."
  starship preset nerd-font-symbols -o "$HOME/.config/starship.toml"
  echo "  ✅  Starship config written to ~/.config/starship.toml"
else
  echo "  ✅  ~/.config/starship.toml already exists — not overwriting"
fi

# ── 5. bash-insulter ─────────────────────────────────────────────────────────
# Randomly insults you when you type an invalid command.
# Sourced in .zshrc if /etc/bash.command-not-found exists.
# https://github.com/hkbakke/bash-insulter
INSULTER_FILE="/etc/bash.command-not-found"
if [[ ! -f "$INSULTER_FILE" ]]; then
  echo "── Installing bash-insulter..."
  TMPDIR=$(mktemp -d)
  git clone --depth 1 https://github.com/hkbakke/bash-insulter.git "$TMPDIR/bash-insulter"
  sudo cp "$TMPDIR/bash-insulter/src/bash.command-not-found" "$INSULTER_FILE"
  rm -rf "$TMPDIR"
  echo "  ✅  bash-insulter installed to $INSULTER_FILE"
else
  echo "  ✅  bash-insulter already installed"
fi

# ── 6. fortune (for the aliases file) ────────────────────────────────────────
# The `fortune` alias points to ~/.config/myquotes/quotes — create the dir.
mkdir -p "$HOME/.config/myquotes"
if [[ ! -f "$HOME/.config/myquotes/quotes" ]]; then
  echo "  ℹ️   ~/.config/myquotes/quotes not found."
  echo "      Create it with one quote per paragraph, separated by %%"
  echo "      Then run: strfile ~/.config/myquotes/quotes"
fi

# ── 7. Symlink .zshrc and .zsh_aliases from dotfiles ─────────────────────────
echo "── Linking Zsh dotfiles..."

_link() {
  local src="$1" tgt="$2"
  # Source must exist
  [[ ! -e "$src" ]] && { echo "  ⚠️  Source not found: $src"; return; }
  # The expanded version of the previous snippet is as follows,
  # if [[ ! -e "$src" ]]; then
  #   printf "  ⚠️  Source not found: %s\n" "$src"
  #   return 1
  # fi

  # Ensure parent directory exists
  mkdir -p "$(dirname "$tgt")"
  # If target exists, handle backup
  [[ -e "$tgt" || -L "$tgt" ]] && mv "$tgt" "${tgt}.bak" && echo "  📦  Backed up: $tgt"
  ln -s "$src" "$tgt"
  echo "  🔗  $tgt → $src"
}

_link "$DOTFILES/myArch/config/zsh/.zshrc"       "$HOME/.zshrc"
_link "$DOTFILES/myArch/config/zsh/.zsh_aliases" "$HOME/.zsh_aliases"

# ── 8. Set Zsh as default shell ───────────────────────────────────────────────
ZSH_BIN="$(which zsh)"
if [[ "$SHELL" != "$ZSH_BIN" ]]; then
  echo "── Setting Zsh as default shell..."
  # chsh requires zsh to be in /etc/shells
  if ! grep -qx "$ZSH_BIN" /etc/shells; then
    echo "$ZSH_BIN" | sudo tee -a /etc/shells
  fi
  chsh -s "$ZSH_BIN"
  echo "  ✅  Default shell set to $ZSH_BIN (takes effect after next login)"
else
  echo "  ✅  Zsh is already the default shell"
fi

echo
echo "✅  12-zsh: done"
echo "    Log out and back in (or: exec zsh) to start using the new shell"
Bitcoin donation

JustToThePoint Copyright © 2011 - 2026 Anawim. ALL RIGHTS RESERVED. Bilingual e-books, articles, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun. Social Issues, Join us.

This website uses cookies to improve your navigation experience.
By continuing, you are consenting to our use of cookies, in accordance with our Cookies Policy and Website Terms and Conditions of use.