If you find yourself in a hole, stop digging, Will Rogers

Welcome to the final chapter of the Arch Linux post-installation series. In this guide, we are going to polish the system with a suite of essential extra tools: the lightning-fast terminal file manager yazi, the Wayland-native wallpaper daemon swww, fun and useful utilities like bash-insulter and LocalSend. We then dive deep into customizing the Noctalia environment, setting up avatars, dynamic wallpapers, and location-based weather. Finally, to ensure your hard work is safe, we conclude by establishing a robust three-tier backup strategy using Clonezilla for disk imaging, rsync for dotfile synchronization, and Timeshift for system-level snapshots.
#!/usr/bin/env bash
# ── 17-extras.sh ──────────────────────────────────────────────────────────────
# Install and configure extra tools:
# 1. yazi — terminal file manager with image previews
# 2. swww — Wayland wallpaper daemon with transitions
# 3. bash-insulter — insults you on wrong commands
# 4. fortunes — install fortune-mod, link your custom collection
# 5. LocalSend — LAN file transfer
# 6. GIMP — image editor
# 7. Ristretto — fast image viewer
# 8. Noctalia — configuration (avatar, wallpaper, weather)
#
# Run: bash 17-extras.sh
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
DOTFILES="$HOME/dotfiles"
MYARCH="$DOTFILES/myArch"
# ── 1. yazi — terminal file manager ──────────────────────────────────────────
# yazi is written in Rust. Supports image preview via kitty protocol (best
# on Wayland), sixel, and chafa fallback.
# Integrates with: fd, ripgrep, fzf, bat, zoxide, imagemagick.
echo "── Installing yazi..."
paru -S --needed --noconfirm \
yazi \
ffmpegthumbnailer \
jq \
poppler \
fd \
ripgrep \
fzf \
zoxide \
imagemagick
echo " ✅ yazi $(yazi --version 2>/dev/null | head -1)"
# yazi config directory — create it so yazi doesn't complain on first run
mkdir -p "$HOME/.config/yazi"
# yazi theme — link from dotfiles if it exists, otherwise create a minimal one
if [[ -f "$MYARCH/config/yazi/theme.toml" ]]; then
ln -sf "$MYARCH/config/yazi/theme.toml" "$HOME/.config/yazi/theme.toml"
echo " 🔗 yazi theme linked"
fi
# ── 2. swww — Wayland wallpaper daemon with animated transitions ──────────────
echo "── Installing swww..."
paru -S --needed --noconfirm swww
echo " ✅ swww $(swww --version 2>/dev/null)"
# ── 3. bash-insulter ─────────────────────────────────────────────────────────
# bash-insulter prints a random insult when you type an invalid command.
# Source: https://github.com/hkbakke/bash-insulter
# Installed to /etc/bash.command-not-found, sourced from .zshrc.
INSULTER="/etc/bash.command-not-found"
DOTFILES_INSULTER="$MYARCH/config/zsh/bash.command-not-found"
if [[ -f "$DOTFILES_INSULTER" ]]; then
echo "── Installing bash-insulter from dotfiles..."
sudo cp "$DOTFILES_INSULTER" "$INSULTER"
sudo chmod 644 "$INSULTER"
echo " ✅ $INSULTER installed from dotfiles"
elif [[ ! -f "$INSULTER" ]]; then
echo "── Cloning bash-insulter from GitHub..."
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"
sudo chmod 644 "$INSULTER"
# Also save to dotfiles for future use
mkdir -p "$(dirname "$DOTFILES_INSULTER")"
cp "$INSULTER" "$DOTFILES_INSULTER"
rm -rf "$TMPDIR"
echo " ✅ bash-insulter installed and saved to dotfiles"
else
echo " ✅ bash-insulter already at $INSULTER"
fi
# ── 4. fortune-mod — required to read fortune files ──────────────────────────
# fortune-mod provides the `fortune` binary that reads from .dat index files.
# strfile creates the .dat binary index from a text fortune file.
# Format: quotes separated by a line containing only %%
echo "── Installing fortune-mod..."
sudo pacman -S --needed --noconfirm fortune-mod
FORTUNE_SRC="$MYARCH/config/zsh/fortune"
FORTUNE_DST="$HOME/dotfiles/myArch/config/zsh/fortune"
if [[ -f "$FORTUNE_SRC" ]]; then
# Build the binary index (.dat file) required by fortune
# strfile reads the text file and creates fortune.dat alongside it
strfile "$FORTUNE_SRC" "${FORTUNE_SRC}.dat" 2>/dev/null || true
echo " ✅ Fortune index built"
echo " ✅ fortune count: $(grep -c '%%' "$FORTUNE_SRC" 2>/dev/null || echo '?') quotes"
fi
# ── 5. LocalSend ─────────────────────────────────────────────────────────────
# LAN file transfer. It is cross-platform (Windows/macOS/Android/Linux).
echo "── Installing LocalSend..."
paru -S --needed --noconfirm localsend-bin
echo " ✅ LocalSend installed"
# ── 6. GIMP ───────────────────────────────────────────────────────────────────
echo "── Installing GIMP..."
sudo pacman -S --needed --noconfirm gimp
echo " ✅ GIMP $(gimp --version 2>/dev/null | head -1)"
# ── 7. Ristretto ─────────────────────────────────────────────────────────────
# Lightweight Xfce image viewer.
echo "── Installing Ristretto..."
sudo pacman -S --needed --noconfirm ristretto
echo " ✅ Ristretto installed"
# ══════════════════════════════════════════════════════════════════════════════
# 10. NOCTALIA CONFIGURATION
# ══════════════════════════════════════════════════════════════════════════════
#
# Noctalia reads ~/.config/noctalia/settings.json on startup and watches it
# with a FileView — changes are picked up within ~500ms without restarting.
#
# We use `jq` (installed in step 1) to merge our keys into whatever Noctalia
# already wrote, preserving all other values (* = object merge in jq).
echo "── Configuring Noctalia..."
# ── Profile picture ───────────────────────────────────────────────────────────
# ~/.face — read by greetd/tuigreet for the login screen avatar
# ~/.face.icon — fallback for SDDM and AccountsService
# Noctalia general.avatarImage — shown on lock screen + profile card
AVATAR_SRC="$HOME/dotfiles/docs/misImagenes/maximoAnawim.jpeg"
if [[ -f "$AVATAR_SRC" ]]; then
# Copy (not symlink) — some login managers reject symlinks for security
cp "$AVATAR_SRC" "$HOME/.face"
cp "$AVATAR_SRC" "$HOME/.face.icon"
echo " ✅ ~/.face and ~/.face.icon → $AVATAR_SRC"
else
echo " ⚠️ Avatar not found: $AVATAR_SRC"
echo " Place your photo there and re-run, or set it in Noctalia Settings GUI"
AVATAR_SRC="" # leave avatarImage empty so Noctalia uses its default
fi
# ── settings.json ─────────────────────────────────────────────────────────────
NOCTALIA_CFG="$HOME/.config/noctalia/settings.json"
mkdir -p "$HOME/.config/noctalia"
# If Noctalia has never run, settings.json does not exist yet.
# Create an empty object so jq has something to merge into.
# Noctalia will add its own defaults on first launch and merge with what we want to change.
if [[ ! -f "$NOCTALIA_CFG" ]]; then
echo '{}' > "$NOCTALIA_CFG"
echo " ℹ️ Created empty settings.json — Noctalia will populate defaults on first run"
fi
# jq merge strategy:
# (.section // {}) ensures we don't fail if a section is missing entirely.
# * merges objects: right-hand keys overwrite, others are preserved.
# --arg passes shell strings; numbers are embedded directly in the filter.
jq \
--arg avatar "$AVATAR_SRC" \
--arg walldir "$HOME/wallpaper" \
'
.general = (.general // {}) * {
"avatarImage": $avatar,
"language": "en",
"telemetryEnabled": false,
"showChangelogOnStartup": false
} |
.wallpaper = (.wallpaper // {}) * {
"directory": $walldir,
"enabled": true,
"randomEnabled": true,
"randomIntervalSec": 600,
"transitionDuration": 1200,
"fillMode": "crop",
"monitorDirectories": [
{
"directory": "/home/nmaximo7/wallpaper",
"name": "HDMI-A-1",
"wallpaper": ""
}
]
} |
# Noctalia weather uses open-meteo.com — no API key required.
# lat/lon are numbers (not strings) — jq embeds them without quotes.
.weather = (.weather // {}) * {
"city": "Málaga",
"latitude": 36.72,
"longitude": -4.42,
"useMetric": true
}
' \
"$NOCTALIA_CFG" > "${NOCTALIA_CFG}.tmp" \
&& mv "${NOCTALIA_CFG}.tmp" "$NOCTALIA_CFG"
echo " ✅ $NOCTALIA_CFG updated"
echo
echo "✅ 17-extras: done"
echo
echo "── Summary of what was installed:"
echo " yazi — terminal file manager (y to open, q to quit)"
echo " swww — wallpaper daemon (Mod+W = random wallpaper)"
echo " insulter — /etc/bash.command-not-found"
echo " fortune — $(command -v fortune 2>/dev/null || echo 'not found')"
echo " GIMP — image editor"
echo " Ristretto — image viewer"
echo " Noctalia — configuration updated"
echo
archclone directory for reference); operation: savedisk, save local disk as an image (save an image of the whole local drive, e.g., /dev/sdc, TS256GMTS800, 238,47 SSD). Options: Skip checking/repairing source file system (faster, safe if system is healthy) and Yes, check the saved image.rsync \
--archive \ # Preserve structure, symlinks, timestamps
--verbose \ # Show progress
--human-readable \ # Readable output
--delete \ # Mirror destination (remove deleted files)
--no-perms \ # Avoid permission issues across systems
--no-group \ # Avoid group mismatches
--omit-dir-times \ # Prevent unnecessary timestamp updates
--exclude='.git/' \
--exclude='*.pyc' \
--exclude='__pycache__/' \
--exclude='*.tmp' \
"$DOTFILES_SRC/" \
"$DOTFILES_DST/"
sudo pacman timeshift.