JustToThePoint English Website Version
JustToThePoint en español

Arch Linux Post-Installation: Dotfiles Symlinking and Pacman Configuration

When the world says, ‘Give up,’ hope whispers, ‘Try it one more time’, Lyndon B. Johnson

VPN

Introduction

With the core graphical environment (Niri, greetd, and Noctalia) installed, the focus shifts to system organization and maintenance. A robust Arch Linux setup relies on version-controlled configuration files and an optimized package manager.

The following scripts finalize the post-installation process: the first creates a symlink farm, linking your dotfiles (stored in a git repository) directly to their expected system locations. This ensures that changes made in your dotfiles directory are immediately live. The second script optimizes pacman itself—enabling color output, parallel downloads, and the multilib repository—while using reflector to identify the fastest mirrors and installing a suite of essential command-line tools.

#!/usr/bin/env bash
# ── 05-symlinks.sh ────────────────────────────────────────────────────────────
# Symlink dotfiles/myArch/config/* into the live config locations.
#
# Philosophy:
#   Config files live in dotfiles/myArch/config/ (version controlled).
#   ~/.config/ and /etc/ contain SYMLINKS to those files.
#   Editing dotfiles/myArch/config/niri/config.kdl IS editing the live config.
#   git diff always shows your real config state.
#  Features: Idempotent operation (safe to run multiple times)
# Symlink map:
#   config/niri/config.kdl           → ~/.config/niri/config.kdl
#   config/noctalia/settings.json    → ~/.config/noctalia/settings.json
#   config/fastfetch/config.jsonc    → ~/.config/fastfetch/config.jsonc
#   config/alacritty/dracula.toml    → ~/.config/alacritty/dracula.toml
#   config/rofi/rounded-dark.rasi    → ~/.config/rofi/rounded-dark.rasi
#   config/rofi/rounded-common.rasi  → ~/.config/rofi/rounded-common.rasi
#   config/espanso/base.yml          → ~/.config/espanso/match/base.yml
#   config/greetd/config.toml        → /etc/greetd/config.toml  (sudo)
#   /mnt/data/dotfiles/wallpaper/    → ~/wallpaper/  (copied, not linked)
#
# Run: bash 05-symlinks.sh
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail

# ── Paths ─────────────────────────────────────────────────────────────────────
DOTFILES="$HOME/dotfiles/myArch/config"
WALLPAPERSDIR="/mnt/data/dotfiles/wallpapers"

# ── Helper ────────────────────────────────────────────────────────────────────
# link SOURCE TARGET
#   Creates TARGET as a symlink to SOURCE.
#   If TARGET already exists (file, dir, or broken symlink) it is backed up
#   with a .bak suffix before the new symlink is created.
link() {
  local src="$1" # src = the real file inside your dotfiles repo
  local tgt="$2" # tgt = where the symlink should be created

  # Ensure the source file actually exists in dotfiles
  # This prevents creating a broken symlink if you mistype a filename or forget to include a file in your repo.
  if [[ ! -e "$src" ]]; then
    echo "  ⚠️  Source not found, skipping: $src"
    return 0
  fi

  # Create parent directory of target if it doesn't exist
  # If you want to link something into ~/.config/nvim/init.lua, but ~/.config/nvim/ doesn’t exist yet, this creates it automatically
  mkdir -p "$(dirname "$tgt")"

  # Back up any existing file/symlink at the target path
  # This makes the script safe and reversible
  if [[ -e "$tgt" || -L "$tgt" ]]; then
    local backup="${tgt}.bak"
    echo "  📦  Backing up existing: $tgt → $backup"
    mv "$tgt" "$backup"
  fi

  # Create the symlink and print a friendly message.
  ln -s "$src" "$tgt"
  echo "  🔗  $tgt → $src"
}

# ── User config symlinks ──────────────────────────────────────────────────────
echo "── Linking user configs..."

# ── Compositor ────────────────────────────────────────────────────────────────
link "$DOTFILES/niri/config.kdl" \
     "$HOME/.config/niri/config.kdl"

# ── Shell ─────────────────────────────────────────────────────────────────────
link "$DOTFILES/noctalia/settings.json" \
     "$HOME/.config/noctalia/settings.json"

link "$DOTFILES/fastfetch/config.jsonc" \
     "$HOME/.config/fastfetch/config.jsonc"

# ── Alacritty theme ───────────────────────────────────────────────────────────
link "$DOTFILES/alacritty/dracula.toml" \
     "$HOME/.config/alacritty/dracula.toml"

# ── Rofi themes ───────────────────────────────────────────────────────────────
link "$DOTFILES/rofi/rounded-dark.rasi" \
     "$HOME/.config/rofi/rounded-dark.rasi"

link "$DOTFILES/rofi/rounded-common.rasi" \
     "$HOME/.config/rofi/rounded-common.rasi"

# ── Espanso ───────────────────────────────────────────────────────────────────
# Espanso looks for matches in ~/.config/espanso/match/
link "$DOTFILES/espanso/base.yml" \
     "$HOME/.config/espanso/match/base.yml"

# ── Wallpapers ────────────────────────────────────────────────────────────────
# Copy (not symlink) wallpapers so they're available even without dotfiles
install -d "$HOME/wallpaper"
if [[ -d "$WALLPAPERSDIR" ]]; then
  cp -rnt "$HOME/wallpaper" "$WALLPAPERSDIR"/* 2>/dev/null || true
  # A trailing slash on the source is required to say "copy the content"
  echo "  🖼️ Copy my wallpapers to  ~/wallpaper/"
fi

# Noctalia's expected directory for wallpapers is $HOME/Pictures/Wallpapers
link "$HOME/wallpaper" "$HOME/Pictures/Wallpapers"

# ── System config symlinks (require sudo) ─────────────────────────────────────
echo "── Linking system configs (sudo required)..."

# greetd config lives in /etc — not user-writable.
# We symlink it so it stays in version control.
# NOTE: if you edit /etc/greetd/config.toml via sudo, the change goes
# directly into dotfiles/myArch/config/greetd/config.toml — no copying needed.
sudo mkdir -p /etc/greetd
sudo ln -sf "$DOTFILES/greetd/config.toml" /etc/greetd/config.toml
echo "  🔗  /etc/greetd/config.toml → $DOTFILES/greetd/config.toml"

# ── Verify all symlinks ───────────────────────────────────────────────────────
echo
echo "── Verification:"
for path in \
  "$HOME/.config/niri/config.kdl" \
  "$HOME/.config/noctalia/settings.json" \
  "$HOME/.config/fastfetch/config.jsonc" \
  "$HOME/.config/alacritty/dracula.toml" \
  "$HOME/.config/rofi/rounded-dark.rasi" \
  "$HOME/.config/rofi/rounded-common.rasi" \
  "$HOME/.config/espanso/match/base.yml" \
  "/etc/greetd/config.toml"
do
  if [[ -L "$path" ]]; then
    echo "  ✅  $path → $(readlink "$path")"
  else
    echo "  ❌  $path is NOT a symlink"
  fi
done

echo
echo "✅  05-symlinks: done"
echo "    Reboot and log in via greetd → select Niri session"
#!/usr/bin/env bash
# ── 06-pacman.sh ──────────────────────────────────────────────────────────────
# Configure pacman, refresh mirrors with reflector, and install base essentials.
#
# IDEMPOTENT — safe to run multiple times. Every step checks current state
# before acting, so re-running produces no side effects.
#
# What this script does:
#   1. Tweak /etc/pacman.conf — colour output, ILoveCandy progress bar,
#      enable the [multilib] repo (needed for 32-bit libs, Steam, Wine)
#   2. Refresh pacman keyring
#   3. Set up reflector for fast Spanish mirrors and run a full system update
#   4. Install a curated set of essential CLI tools
#   5. Configure passwordless sudo for your user
#
# Run: bash 06-pacman.sh
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail

USERNAME="${SUDO_USER:-$(whoami)}"   # works whether called with sudo or not

# ── Idempotency helper ────────────────────────────────────────────────────────
# already_in FILE PATTERN
#   Returns 0 (true) if PATTERN exists as a non-commented line in FILE.
#   Used before every sed insertion to avoid duplicating lines.
already_in() {
  local file="$1" pattern="$2"
  grep -qE "^[[:space:]]*${pattern}" "$file" 2>/dev/null
}

# ── 1. pacman.conf tweaks ─────────────────────────────────────────────────────
echo "── Configuring /etc/pacman.conf..."

# Enable Color output
# sed guard: only uncomment if the line is currently commented out
if ! already_in /etc/pacman.conf "Color"; then
  sudo sed -i '/^#Color/s/^#//' /etc/pacman.conf
  echo "  ✅  Color enabled"
else
  echo "  ⏭️   Color already enabled"
fi

# ILoveCandy: replaces [####...] with a Pac-Man animation.
# IDEMPOTENCY: check before inserting
if ! already_in /etc/pacman.conf "ILoveCandy"; then
  sudo sed -i '/^Color/a ILoveCandy' /etc/pacman.conf
  echo "  ✅  ILoveCandy enabled"
else
  echo "  ⏭️   ILoveCandy already enabled"
fi

# Enable [multilib] repository (32-bit libs, Steam, Wine).
# The block appears as:
#   #[multilib]
#   #Include = /etc/pacman.d/mirrorlist
# sed matches from [multilib] to the Include line and removes leading #.
# Idempotent: if already uncommented, s/^#// matches nothing and does nothing.
if ! already_in /etc/pacman.conf '\[multilib\]'; then
  sudo sed -i "/\[multilib\]/,/Include/"'s/^#//' /etc/pacman.conf
  echo "  ✅  [multilib] enabled"
else
  echo "  ⏭️   [multilib] already enabled"
fi

# Enable ParallelDownloads — download up to 5 packages simultaneously.
# Cuts update times by 60-80% on fast connections.
# Idempotent: only matches the commented-out form (#ParallelDownloads = 5).
if ! already_in /etc/pacman.conf "ParallelDownloads"; then
  sudo sed -i 's/^#ParallelDownloads = 5/ParallelDownloads = 5/' /etc/pacman.conf
  echo "  ✅  ParallelDownloads enabled"
else
  echo "  ⏭️   ParallelDownloads already enabled"
fi

echo "  ✅  pacman.conf configured"

# ── 2. Refresh pacman keyring ─────────────────────────────────────────────────
# The keyring can become stale on a fresh install — rotating Arch keys can
# cause "invalid or corrupted package" errors during installation.
# --refresh-keys fetches updated keys from the keyserver.
# Idempotent: re-running only refreshes keys, never breaks existing ones.
echo "── Refreshing pacman keyring..."
sudo pacman-key --init
sudo pacman-key --populate archlinux
sudo pacman-key --refresh-keys 2>/dev/null \
  || echo "  ⚠️   Key refresh had warnings — usually harmless"

# ── 3. Reflector — fastest mirrors for Spain ──────────────────────────────────
# reflector benchmarks mirror candidates and writes the fastest to mirrorlist.
# Idempotent: always overwrites /etc/pacman.d/mirrorlist with fresh results.
#
# Flags:
#   --country 'Spain'  only benchmark Spanish mirrors
#   --latest 20        from the 20 most recently synced (avoids stale mirrors)
#   --sort rate        sort by download speed, fastest first
#   --save             write to mirrorlist
echo "── Installing and configuring reflector..."
sudo pacman -S --needed --noconfirm reflector

sudo reflector \
  --country Spain,Portugal,France,Germany \
  --protocol https \
  --latest 20 \
  --sort rate \
  --save /etc/pacman.d/mirrorlist

echo "  ✅  Mirrorlist updated with fastest Spanish mirrors"

# Enable reflector.timer so mirrors are refreshed weekly automatically.
# Idempotent: enable --now is safe to run on an already-enabled timer.
sudo systemctl enable --now reflector.timer

# ── 4. Full system update ─────────────────────────────────────────────────────
# Always run AFTER changing the mirrorlist.
# Partial upgrades (updating mirrorlist but not packages) cause library mismatches.
# -Syu: sync databases (-S), refresh (-y), upgrade all (-u).
# Idempotent: if everything is up to date, pacman does nothing.
echo "── Running full system update..."
sudo pacman -Syu --noconfirm

# ── 5. Essential packages ─────────────────────────────────────────────────────
# --needed makes this idempotent: already-installed packages are skipped.
echo "── Installing essential packages..."

BASE=(
  base-devel          # make, gcc, binutils — required for AUR and compilation
  git                 # version control
  rsync               # fast file sync / backup (used by maintain-arch.sh)
  wget                # non-interactive download
  curl                # HTTP client
  unzip               # archive extraction
  p7zip               # 7z archives
  tree                # directory tree display
)

SHELL=(
  zsh                 # shell
  bash-completion     # tab completion for bash (fallback shell)
  starship            # cross-shell prompt
)

EDITORS=(
  neovim              # Text editor
)

SYSTEM=(
  fastfetch           # system info display (neofetch successor)
  btop                # resource monitor
  man-db              # man pages
  man-pages           # Linux man pages
  tldr                # simplified man pages
)

CLI=(
  eza                 # modern ls with icons and colours
  bat                 # modern cat with syntax highlighting
  fd                  # modern find
  ripgrep             # modern grep
  fzf                 # fuzzy finder
  zoxide              # smart cd that learns your habits
  jq                  # JSON processor
)

sudo pacman -S --needed --noconfirm \
  "${BASE[@]}" \
  "${SHELL[@]}" \
  "${EDITORS[@]}" \
  "${SYSTEM[@]}" \
  "${CLI[@]}"

echo "  ✅  Essential packages installed"

# ── 6. Passwordless sudo ──────────────────────────────────────────────────────
# Adds a sudoers drop-in granting your user NOPASSWD for all commands.
#
# WHY a drop-in in /etc/sudoers.d/ instead of editing /etc/sudoers directly:
#   A syntax error in /etc/sudoers locks you out of sudo permanently.
#   Drop-in files are validated independently and can be deleted if broken.
#
# IDEMPOTENCY: the file is only written if it does not already exist,
#   OR if it exists but does not contain the expected NOPASSWD line.
#   This prevents overwriting a customised sudoers entry on re-runs.
#
SUDOERS_FILE="/etc/sudoers.d/99-${USERNAME}-nopasswd"
SUDOERS_LINE="${USERNAME} ALL=(ALL:ALL) NOPASSWD: ALL"

echo "── Configuring passwordless sudo for: $USERNAME"

if [[ -f "$SUDOERS_FILE" ]] && grep -qF "$SUDOERS_LINE" "$SUDOERS_FILE" 2>/dev/null; then
  echo "  ⏭️   Passwordless sudo already configured for $USERNAME"
else
  echo "$SUDOERS_LINE" | sudo tee "$SUDOERS_FILE" > /dev/null

  # sudoers files MUST be mode 0440 — sudo refuses world-writable drop-ins
  sudo chmod 0440 "$SUDOERS_FILE"

  # Validate before accepting — syntax errors are caught here, not at next login
  if sudo visudo -c -f "$SUDOERS_FILE" 2>/dev/null; then
    echo "  ✅  Passwordless sudo enabled for $USERNAME"
  else
    echo "  ❌  Syntax error in sudoers drop-in — removing it"
    sudo rm -f "$SUDOERS_FILE"
    exit 1
  fi
fi

echo
echo "✅  06-pacman: done"
echo "    Next: bash 07-locale.sh"
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.