Contents

Dotfile Management and Terminal power tools

terminaldotfileslinuxdevopscli-tools

This is a summary of a talk about dotfiles and terminal tools. You can see examples in my dotfiles and infrastructure.

Modern Drop-in Replacements for Classic Unix Tools

Several modern tools serve as superior replacements for classic Unix commands. They’re typically written in Rust, faster, and have better defaults.

eza instead of ls

eza is a drop-in replacement for ls with color-coding, icons, and sensible defaults:

alias ls="eza --icons --group-directories-first -la"

bat instead of cat

bat adds syntax highlighting, line numbers, and paging (it replaces both cat and less):

alias cat="bat"

fd instead of find

fd is a simpler, faster alternative to find:

# Classic find:
find . -name "*.py" -type f

# fd equivalent:
fd -e py

ripgrep (rg) instead of grep

ripgrep is significantly faster, respects .gitignore, and has better defaults.

delta instead of git diff

delta provides beautiful, side-by-side diffs with syntax highlighting. Set it up in your ~/.gitconfig:

[core]
    pager = delta

[interactive]
    diffFilter = delta --color-only

jq for JSON

jq is a command-line JSON processor. Pipe any JSON into it for pretty-printing and filtering:

# Pretty-print a minified JSON file
cat tsconfig.json | jq

# Extract a specific field
cat tsconfig.json | jq '.compilerOptions.target'

# Filter arrays, transform data
curl -s https://api.example.com/data | jq '.results[] | {name, id}'

fzf: The Swiss Army Knife of Fuzzy Finding

fzf is a general-purpose fuzzy finder. It takes any list of strings as input via pipe and provides an interactive selection interface. The selected item is then piped to the next command.

Basic usage

# Pipe anything into fzf
echo -e "alpha\nbeta\ngamma" | fzf

# Use the selected value
echo -e "alpha\nbeta\ngamma" | fzf | wc -w

Interactive git branch switching

# Add to your .zshrc
gco() {
  git branch | fzf | xargs git checkout
}

fzf integrates with shell history out of the box (typically bound to Ctrl+R). Your ~/.zsh_history is just a text file — fzf makes it searchable:

cat ~/.zsh_history | fzf

Fuzzy tmux session selector

tt() {
  tmuxinator list | tail -n +2 | fzf | xargs tmuxinator start
}

fzf can be piped into GUI launchers too. On Linux, tools like walker or dmenu work the same way — they accept piped input and output the selection. On macOS, choose-gui serves the same purpose.

lazygit and lazydocker

lazygit is a full-featured terminal UI for git. You can browse commits, stage/unstage files, stash, cherry-pick, switch branches — all via keyboard shortcuts. Press ? to see all keybindings.

lazydocker is the same concept for Docker. View running containers, inspect logs, exec into containers, restart services, check stats — all from a terminal UI.

Both ship as single binaries, making them easy to install even on remote machines.

alias lg="lazygit"
alias lzd="lazydocker"

Dotfile Management with GNU Stow

GNU Stow manages your dotfiles by creating symlinks. The idea: keep all your config files in a git repository (e.g., ~/dotfiles/) that mirrors your home directory structure, and stow creates the symlinks for you.

~/dotfiles/
  _common/
    .zshrc
    .ssh/config
    .config/
      alacritty/alacritty.toml
      ...
  _linux/
    .config/
      hyprland/...
  _mac/
    ...

Running stow symlinks everything into your home directory:

stow  -t "$HOME" _common
stow -t "$HOME" _linux # or mac

Your .zshrc in ~ becomes a symlink to ~/dotfiles/_common/.zshrc. Now all configs are version-controlled. You can split configs into common, linux, and mac directories for platform-specific settings.

You can also keep your .zshrc clean by splitting it into multiple sourced files:

# ~/.zshrc
source ~/.config/zsh/settings.zsh
source ~/.config/zsh/aliases.zsh
source ~/.config/zsh/variables.zsh
source ~/.config/zsh/plugins.zsh
source ~/.config/zsh/secrets.zsh

just: A Modern Task Runner

just is a command runner inspired by make, but without Makefile’s quirks (where every indent is significant and targets are expected to produce files). It’s essentially a simpler, more readable Makefile.

# Justfile
init:
  pre-commit install

[linux]
sync:
  stow  -t "$HOME" _common
  stow -t "$HOME" _linux
[macos]
sync:
  stow  -t "$HOME" _common
  stow -t "$HOME" _mac


[linux]
unsync:
  stow  -D -t "$HOME" _common
  stow  -D -t "$HOME" _linux

[macos]
unsync:
  stow  -D -t "$HOME" _common
  stow -D -t "$HOME" _mac
just sync       # "just sync" -- reads like English

It supports variables, dependencies between tasks, OS-specific recipes, and parameters.

mise: Universal Version Manager

mise (from French “mise en place”) is a polyglot version manager that replaces pyenv, nvm, fnm, rbenv, and similar tools. One tool to manage versions of Python, Node.js, Go, Terraform, and hundreds more.

Create a mise.toml in your project root:

[tools]
python = "3.12"
node = "lts"
pnpm = "latest"

Then:

mise install

The key feature: per-directory tool versions. When you cd into a project, mise activates the correct versions. A different project can use different versions of the same tools.

# In project-a/ -- Python 3.12, Node 20
cd project-a && which python
# ~/.local/share/mise/installs/python/3.12.0/bin/python

# In project-b/ -- Python 3.11, Node 18
cd project-b && which python
# ~/.local/share/mise/installs/python/3.11.0/bin/python

You can also set global tool versions:

# ~/.config/mise/config.toml
[tools]
python = "latest"
node = "lts"
go = "latest"
fzf = "latest"
fd = "latest"
ripgrep = "latest"

Think of it as a universal virtualenv/conda for all programming languages and CLI tools.

tmuxinator: Predefined tmux Layouts

tmuxinator lets you define tmux session layouts in YAML files. Instead of manually splitting panes every time, define your workspace once:

# ~/.config/tmuxinator/myproject.yml
name: myproject
root: ~/Projects/myproject

windows:
  - editor:
      layout: main-horizontal
      panes:
        - nvim
        - ''  # empty terminal
  - server:
      panes:
        - npm run dev
  - logs:
      panes:
        - tail -f logs/app.log
tmuxinator start myproject
tmuxinator stop myproject

yazi: Terminal File Manager

yazi is a fast terminal file manager with tabs, file previews (including images), and vim-like keybindings.

System Setup with Ansible

Ansible is a configuration management tool that lets you declaratively describe the desired state of a system. Instead of writing imperative scripts (“run this command, then that command”), you describe what the system should look like.

You can use it to set up your personal machines from scratch with a single command:

# roles/base/tasks/main.yml
- name: Install base packages
  package:
    name:
      - git
      - neovim
      - tmux
      - zsh
    state: present

- name: Install AUR packages
  aur:
    name:
      - lazygit
      - lazydocker
      - eza
      - bat
    state: present

Organize your setup into roles (base, desktop, gaming, docker, etc.) and hosts (desktop, laptop, server):

# playbook.yml
- hosts: workstations
  roles:
    - base
    - dotfiles
    - desktop

- hosts: desktop
  roles:
    - gaming
    - wake-on-lan

Deploy everything with one command:

just deploy desktop
# runs: ansible-playbook playbook.yml --limit desktop

Ansible has modules for almost anything: package managers, Docker, Cisco switches, OpenWRT routers, systemd services, file templates, and more.

Managing Secrets with SOPS

SOPS (Secrets OPerationS) encrypts only the values in your YAML/JSON files, leaving keys visible. This means your secrets file can live in a public git repository:

# What everyone sees in git (encrypted):
db_password: ENC[AES256_GCM,data:abc123...,type:str]
api_token: ENC[AES256_GCM,data:def456...,type:str]

# What you see after: sops secrets.yml
db_password: my-actual-password
api_token: my-actual-token

SOPS integrates with Ansible’s Jinja2 templates. You can write Docker Compose files as templates with {{ variable }} placeholders that get filled from your encrypted secrets at deploy time.

Syncthing: Peer-to-Peer File Sync

Syncthing synchronizes files between devices without any cloud service. It’s peer-to-peer, free, and encrypted.

Use case: sync notes, papers, or any folder across your desktop, laptop, phone, and home server. Whenever a device comes online, it syncs with the others automatically.

Desktop <---> Home Server <---> Laptop
    \                              /
     +-------> Phone <-----------+

Adding a new device is as simple as scanning a QR code. Your data stays on your devices — no third-party storage involved.

rsync over scp

Prefer rsync over scp for file transfers. scp re-transfers everything from scratch every time. rsync only sends the differences:

rsync -avz --delete ./dist/ user@server:/var/www/html/
  • -a: archive mode (preserves permissions, timestamps, etc.)
  • -v: verbose
  • -z: compress during transfer
  • --delete: remove files on the destination that don’t exist locally

This matters when you’re on a slow connection or transferring large directories.

Essential Terminal Shortcuts

When you can’t install any tools (locked-down environments, restricted servers), these built-in shortcuts still work:

ShortcutAction
fgBring suspended process back to foreground
bgContinue suspended process in background
!!Repeat last command (useful with sudo !!)
!$Last argument of previous command
^old^newRe-run last command, replacing old with new

The !$ trick is handy for sequences like:

mkdir -p /some/deep/path
cd !$
# expands to: cd /some/deep/path

And ^old^new for fixing typos:

grep -r "patern" src/
# typo! fix it:
^patern^pattern
# runs: grep -r "pattern" src/

Comments