How to Set Up a Personal Dev Toolchain on Linux: My Actual Environment

How to Set Up a Personal Dev Toolchain on Linux: My Actual Environment

Most Linux developer toolchain guides list software. This one describes a workflow — the specific tools I use, why I chose them, how they are configured, and how they work together for the kind of work this blog covers: reading and writing systems code, debugging production issues, and exploring open-source codebases. Every choice here is the result of trying the alternatives and finding this configuration more productive.

Shell: zsh + starship + fzf

Bash is fine. zsh is better for interactive use. The specific advantages: better tab completion, globbing extensions (e.g., **/*.c for recursive file matching), and a richer plugin ecosystem. I use starship (https://starship.rs) as the prompt — it is fast (written in Rust), shows git status, Python virtualenv, and exit codes without noticeable latency. Crucially, it is configured in a single TOML file that goes in dotfiles.

fzf (fuzzy finder) is the single highest-leverage tool in my shell. Ctrl-R gives history search with fuzzy matching. Ctrl-T gives file selection. Alt-C gives directory navigation. Every one of these replaces 5-10 keystrokes with 2-3. Install via your distro’s package manager and add the shell integration: source /usr/share/doc/fzf/examples/key-bindings.zsh.

# .zshrc essentials export HISTSIZE=100000 export HISTFILESIZE=100000 setopt HIST_IGNORE_DUPS setopt SHARE_HISTORY source ~/.fzf.zsh eval “$(starship init zsh)”

Editor: Neovim with Lazy.nvim

I use Neovim (not VSCode, not Emacs) because the keybindings are in my muscle memory and the startup time is under 100ms. The plugin manager is lazy.nvim, which loads plugins on demand. For the kind of C and Python source reading this blog covers, the essential plugins are: nvim-treesitter (syntax highlighting that understands code structure), telescope.nvim (fuzzy file and grep navigation, integrates with fzf), and clangd + pyright via nvim-lspconfig for jump-to-definition.

LSP (Language Server Protocol) is what makes modern Neovim genuinely productive for code reading. clangd for C/C++ requires a compile_commands.json — generate it for nginx with: ./configure –with-cc-opt=’-O0 -g’ && bear — make. Then clangd can follow #includes and resolve function calls accurately.

# Generate compile_commands.json for nginx apt install bear cd nginx-source/ ./auto/configure –prefix=/tmp/nginx-build bear — make # Now clangd works correctly for all source files

Terminal: tmux with Persistent Sessions

tmux gives persistent sessions (reconnect after SSH disconnect), split panes for code-on-left/shell-on-right, and named windows for different projects. My tmux config is minimal: prefix key is Ctrl-a (not Ctrl-b), pane splitting uses | and – for vertical and horizontal, and tmux-resurrect saves and restores sessions across reboots.

For code reading sessions, I use a consistent layout: left pane is Neovim with the source, right top pane is a shell for grep/ctags queries, right bottom pane is for running the software and observing its behavior.

Code Navigation: ctags + ripgrep

Universal ctags generates a tags file that Neovim uses for Ctrl-] jump-to-definition without LSP. It is indispensable for codebases that are hard to configure for LSP (older C projects, Makefiles without cmake). Generate tags for nginx: ctags -R –c-kinds=+p –fields=+iaS –extra=+q src/.

ripgrep (rg) replaces grep for code search. It is 5-10x faster than grep on large codebases, respects .gitignore, and handles binary files correctly. rg ‘keepalive_requests’ src/ is my constant companion when reading nginx source. Alias rg to grep in your shell if you want the habit to build automatically.

Python Environment: pyenv + virtualenv

System Python is for the system. Development Python is managed by pyenv, which installs any Python version into ~/.pyenv/versions/ and switches between them per directory via .python-version files. For CPython source reading (Blogs 01-03), I install a debug build: PYTHON_CONFIGURE_OPTS=’–with-pydebug’ pyenv install 3.12.0. The debug build enables sys.gettrace() hooks and adds runtime assertions that help understand interpreter behavior.

# Install pyenv curl https://pyenv.run | bash # Install debug Python PYTHON_CONFIGURE_OPTS=’–with-pydebug –with-valgrind’ \   pyenv install 3.12.0 pyenv global 3.12.0

Git Configuration: The Details That Matter

git log –oneline –graph –all is too long to type. Alias it: git config –global alias.lg ‘log –oneline –graph –all –decorate’. For reading project history (Blog 11’s Step 7), git log -p -S ‘term’ searches for commits that added or removed a string — invaluable for understanding when and why code was written.

Configure git diff to use a better diff algorithm: git config –global diff.algorithm histogram. This produces more readable diffs for refactored code by finding better change boundaries.

Dotfiles: Making It Reproducible

All configuration files (zshrc, tmux.conf, nvim/, gitconfig) live in a dotfiles git repository, symlinked into the home directory with a simple install.sh. This makes setting up a new machine or VM a 10-minute process. The dotfiles repository also serves as a log of configuration decisions — the commit history explains why each setting exists.

Conclusion

A productive Linux developer toolchain is not about having every tool — it is about having the right tools integrated into a coherent workflow. The combination of fzf + Neovim + tmux + ripgrep + ctags creates a code-reading environment where you can move between files, search for symbols, and follow call graphs without leaving the keyboard. Combined with the source-reading methodology in Blog 11, this environment makes any open-source codebase approachable.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *