A Decade of Linux Setups — Why I Stopped Looking for ‘The One’
How I Landed on Fedora
For many years, I used a Debian-based distribution as my primary operating system. I started with Debian itself, and later switched to Ubuntu for convenience. I had a good experience with both, but the more I learnt about the Linux ecosystem, the more I felt I did not need the extra layers Ubuntu puts on top of Debian. I started exploring a variety of popular and expert-level distributions. I wanted a more “pure” Linux experience — closer to the upstream projects and communities. Soon, I realised that the Debian package ecosystem is far wider than the Red Hat ecosystem, and switching to a Red Hat-based distribution such as Fedora or CentOS was difficult because some of the software I used daily was not published as .rpm packages.
Considering simplicity, full control over my system, and the breadth of available packages, Arch Linux eventually made its way onto my list. My first installation attempt was unpleasant, but going through the installation process — feeling that fingerprint of control over every part of the OS — made me warm to it quickly. I could find almost anything in the AUR, spanning from packages available in other distributions to wrapped legacy packages whose development had been discontinued and removed from mainstream repositories. I had a lot of fun, even though I nearly broke my system more than once. But the complexity of installation and personalisation, and the differences between my desktop and server environments, eventually made Arch difficult to sustain. Along the way I tried some Arch-based distributions — Manjaro, EndeavourOS, and BlackArch as an alternative to Kali. All of them were pleasant experiences, but none of them addressed my real problem: a unified ecosystem for both production and personal use.
I had always had a soft spot for Red Hat and was a fan of it for production servers. At the time, Red Hat was not free for personal use, so I went with CentOS to unify my environment. But as a developer who was not yet familiar with containerisation — tools like Podman and Docker — I needed bleeding-edge packages to practise with and keep up with the software I was building. CentOS was not the answer either.
I had always known about Fedora — it has long been one of the pioneer distributions in the Linux ecosystem — but I had run into problems configuring my Nvidia GPU drivers and occasional glitches in video games due to missing or improperly configured components. Around that time, a new distribution was rising: Nobara, a highly optimised Fedora-based distribution built for gaming. If a distribution is tuned for gaming — with up-to-date kernels, fresh GPU drivers, and low-level performance tweaks — it tends to get along well with machine learning setups too, and Nobara became my daily driver for a while. But I eventually realised it is not a community-driven project — it is maintained by a single person. I needed to respect my commitment to the open-source and community-driven spirit, so I started looking for an alternative and stumbled upon the Fedora Post Install Guide.
I thought: why not give Fedora one final, honest try? For a good while, that try looked like the end of my distro-hopping on the desktop. Following the guide transformed Fedora — I have always been a KDE user and have never warmed to GNOME — into an OS that was smooth, stable, and genuinely enjoyable to live in. Everything I had been looking for was there. One problem remained, though: some packages were unavailable in the default repositories, or required adding custom sources. Especially with the recent wave of Rust-based tools for Linux, I had adopted several in my daily workflow that simply were not in DNF.
How I Fixed the Packages Problem
That remaining problem — missing packages — was not unique to Fedora. It had been following me across every distribution I had ever used, in different forms. On Debian it was outdated versions. On Arch it was AUR maintenance risk. On CentOS it was the gap between stability and currency. Fedora brought me closer than anything else had, but it did not fully close it. The answer, when it finally arrived, did not come from the distribution at all.
Before getting to that, I want to talk about my relationship with Python — because the solution to the packages problem and the solution to surviving the Python ecosystem turned out to be the same thing.
Pixi: The Missing Piece for the Python Ecosystem and Beyond
I started my programming journey with Java in 2010. It was one of the most elegant languages available at the time, and I vividly remember compiling Android apps before Android Studio with Eclipse, installing them through a BlueStacks emulator. Later I switched to C/C++ to get closer to the underlying machine — learning the hard way about setting up projects, installing libraries system-wide, writing Makefiles, and eventually CMakeLists.txt. Cross-compiling for another architecture was painful, but it was structured and I understood every piece of it. I never touched package managers like Conan or vcpkg until much later.
Python was initially forced on me by an assignment. I just started writing it — some trial and error — and it worked. There was no AI to answer questions in 2020, so I learnt mostly by searching. Within a year Python had become my default language for assignments and eventually for shell scripting, replacing Bash in many places. Its package manager made the ecosystem feel welcoming, and the breadth of available libraries was remarkable.
To avoid dependency conflicts I kept a set of virtual environments in $HOME/.venv/ — one for computer vision, one for networking, one for scripting, one for signal processing — and activated whichever I needed. It worked, until I tried moving a project between systems. Binary packages were a particular nightmare. Conda was the standard answer for that, but it was too slow and too heavy for my taste. I kept trying to avoid it, compiling packages from source with my own compiler out of old C/C++ habits. Something was fundamentally wrong at the portability level — Python is interpreted, it should be portable, and yet reproducing an environment reliably across machines felt like a constant struggle. I tried requirements.txt, then Poetry, then uv. Both Poetry and uv were excellent tools, but neither handled binary packages independently — I still needed mamba alongside them for torch-based projects, and anything involving torch_scatter could turn into a nightmare.
Then I read a post about pixi somewhere and it immediately caught my attention. I ran to try it — a project manager that handles pip via uv and conda via mamba simultaneously, in a single environment file, without installing a heavy default package set. It worked across platforms: not just x86_64, but aarch64 and others too. You could even add custom channels. I had found what I had been looking for — a single tool to manage everything from a simple shell script to a complex multi-platform project with heavy binary dependencies.
Managing Packages
On Fedora, the default package manager DNF was always the first place I looked, and on openSUSE Tumbleweed zypper plays the same role. For GUI applications, Flatpak has become indispensable — developers tend to keep their Flatpak releases more up to date than distribution repositories, and it covers the applications I use most: Telegram, Discord, and others. I install almost everything GUI-related through Flatpak.
For terminal tools and binary utilities, however, neither DNF nor Flatpak covers everything — particularly the newer wave of Rust-based CLI tools. On remote servers where I have no installation privileges, the situation is even more constrained.
Pixi turned out to solve this problem too. I had initially found its pixi shell activation requirement slightly annoying for tools I wanted available globally. Then I discovered:
pixi global install <package_name>This was a game changer. I could install tools outside any virtual environment — not as project dependencies, but as system-level utilities available anywhere. It became the first command I run on any new machine:
pixi global install zoxide ripgrep fd-find bat eza btm dust ncdu delta hyperfine xh sd neovim tmux zellij btop zsh xonsh skim yazi tokeiNo custom repositories. No extra layers. No searching for obscure PPAs or Copr entries. Everything in one command.
Why I Left Fedora for openSUSE Tumbleweed
Fedora was, honestly, excellent. It was stable, polished, and day to day I rarely had anything to complain about. What eventually wore me down was not breakage — it was the release cycle. Fedora ships a new version roughly every six months, and each release is supported for only about thirteen months, so staying current means a major-version upgrade twice a year. The upgrades themselves usually went fine, but each one still meant setting time aside, checking that third-party repositories and GPU drivers had caught up, and keeping an eye on the end-of-life countdown. On the machine I depend on for my research, that recurring chore became friction I did not want.
I had already tried openSUSE Tumbleweed around the time I was first settling into Fedora, so moving back to it was an easy decision. Tumbleweed is a rolling release, so there is no versioning at all: no “Fedora 43 to 44” migration, no end-of-life date to plan around — the system simply stays current. And despite being rolling, it is the most stable distribution I have used, thanks to openSUSE’s automated testing pipeline (openQA), which gates every snapshot before it ships. It is also one of the most genuinely community-oriented projects in the Linux world, which matters to me for the same reason I left Nobara.
For me, Tumbleweed is the best of both worlds: the freshness of a rolling release, without giving up the stability I had come to rely on with Fedora.
Different Distros for Different Jobs
Tumbleweed is now my desktop daily driver, but the story does not end there — because Linux is not just my desktop. It is also every server I deploy to, every container image I build from, and every machine I do not personally sit in front of.
The fact that openSUSE is SUSE-based rather than Red Hat-based barely matters on the desktop any more — Flatpak and pixi cover the gap regardless of what is underneath. For servers, though, it matters a great deal: if I am deploying to infrastructure running SLES, openSUSE (Tumbleweed, or openSUSE Leap for a non-rolling option) is the natural match, whereas a RHEL-family choice makes more sense alongside RHEL-compatible infrastructure.
Which brings me to AlmaLinux. Most of my Docker base images are AlmaLinux — it is a clean, community-driven RHEL rebuild, and that compatibility matters when the goal is reproducible builds across HPC systems, cloud instances, and local containers that all ultimately trace back to a RHEL lineage. For a long time, AlmaLinux on the desktop felt like a non-starter — too conservative, too server-oriented, missing too much. But with Flatpak filling in GUI applications and pixi filling in everything else, that gap has effectively closed. I now run AlmaLinux on some of my desktops too, and it feels far less like a compromise than it would have a few years ago.
So the honest version of the story is less “I found The One Distro” and more “I stopped needing to find one.” openSUSE Tumbleweed is my desktop — KDE, gaming, day-to-day tinkering — with no upgrade cycle to babysit. AlmaLinux sits underneath almost everything I containerise, and increasingly on desktops too. Fedora remains a distribution I respect and would happily recommend; I simply prefer not to have a release calendar. What used to feel like indecision now feels like having the right tool for the job — and the credit, again, goes to Flatpak and pixi for making the switching cost low enough that it no longer feels like a trade-off.
The Setup
If you are curious about the tools and how I configure them, this section is for you. The nice thing about all of this is that it is entirely distro-agnostic — even my IDEs, Zed (the main one) and VS Code (the reserve), are available everywhere these days. Whether I am on openSUSE Tumbleweed, AlmaLinux, or Fedora, the setup below is identical, because Flatpak, pixi global install, and IDE installers all work the same way regardless of distro.
Terminal Tools
| Tool | What it does |
|---|---|
zoxide |
Smarter cd with directory history and frecency ranking |
ripgrep |
Fast recursive text search (rg) |
fd-find |
Fast, ergonomic find replacement |
bat |
cat with syntax highlighting and Git integration |
eza |
Modern ls replacement with icons and tree view |
btm |
Interactive terminal system monitor |
dust / ncdu |
Fast, intuitive disk usage analyser |
delta |
Improved git diff pager with syntax highlighting |
hyperfine |
CLI benchmarking tool |
tokei |
Fast code statistics — lines of code, comments, and blanks per language |
xh |
Friendly, fast HTTP client |
sd |
Intuitive search-and-replace for the command line |
neovim |
Modal text editor |
zellij / tmux |
Terminal workspace and session managers |
btop |
Beautiful terminal resource monitor |
skim |
Fuzzy finder (sk) for files, history, and more |
yazi |
Fast, terminal-based file manager |
Queuing GPU Work with pueue
One more tool that has quietly become part of my daily workflow is pueue — a lightweight command-line task manager and scheduler. You hand it shell commands, and it queues them up, running them one after another (or in parallel, if you configure multiple workers) regardless of whether you’re still logged in. I use it specifically to keep my GPU busy: I queue up several experiments before I step away from my desk or laptop, and pueue works through them in sequence, starting the next one the moment the previous one finishes — so the GPU keeps working even when I’m not.
Installation is just as easy via pixi, and the daemon only needs enabling once:
# Install pueue (client + daemon) globally via pixi
pixi global install pueue
# Enable and start the daemon as a systemd user service
systemctl --user enable pueued
systemctl --user start pueued
systemctl --user status pueuedFrom there, queuing experiments is as simple as:
pueue add -- pixi run python train.py --config exp1.yaml
pueue add -- pixi run python train.py --config exp2.yaml
pueue statusShell Aliases
I add the following to my .zshrc or .bashrc:
# zoxide must be initialized before the `cd` alias below, otherwise the `z`
# function doesn't exist yet and `cd` fails until you manually re-source
# this file
eval "$(zoxide init zsh)"
# Modern replacements
alias ls="eza --icons --group-directories-first"
alias tree="eza --icons --tree"
alias treed="eza --icons --tree --level"
alias lsdu='du -a -h --max-depth=1 | sort -hr'
alias du='dust'
alias top='btm'
alias cd='z'
alias grep='rg'
alias vim='nvim'
alias search='sk'
# Pipe --help and -h output through bat for syntax highlighting
alias -g -- -h='-h 2>&1 | bat --language=help --style=plain'
alias -g -- --help='--help 2>&1 | bat --language=help --style=plain'
# Set of general useful functions
# Create directory and instantly cd into it
mkcd() {
mkdir -p "$1" && cd "$1"
}
# Easily move UP 'N' directories (e.g., 'up 3' moves up 3 levels)
up() {
local levels="${1:-1}"
local path=""
for ((i=1; i<=levels; i++)); do
path="../$path"
done
cd "$path"
}
# Check public and local IP addresses cleanly
myip() {
local local_ip=$(hostname -I 2>/dev/null | awk '{print $1}' || ipconfig getifaddr en0 2>/dev/null || echo "Unknown")
echo "Local IP: $local_ip"
echo "Public IP: $(curl -s https://ifconfig.me)"
echo "Location: $(curl -s https://ipapi.co/city/), $(curl -s https://ipapi.co/country_name/)"
}
# Runs a clean multi-record (A, AAAA, MX, NS, TXT) DNS check for a domain.
dnslookup() {
if [[ -z "$1" ]]; then
echo "Usage: dnslookup <domain>"
return 1
fi
echo "=== A / AAAA Records ==="
dig +short "$1" A "$1" AAAA
echo "\n=== MX Records ==="
dig +short "$1" MX
echo "\n=== NS Records ==="
dig +short "$1" NS
echo "\n=== TXT Records ==="
dig +short "$1" TXT
}
# Find which process is listening on a specific port (e.g., 'port 8080')
port() {
if [[ -z "$1" ]]; then
echo "Usage: port <port_number>"
return 1
fi
lsof -i :"$1"
}
# Locates and forcibly terminates (kill -9) whatever process is occupying a given port.
killport() {
if [[ -z "$1" ]]; then
echo "Usage: killport <port_number>"
return 1
fi
local pid=$(lsof -t -i:"$1")
if [[ -n "$pid" ]]; then
echo "Killing process $pid on port $1..."
kill -9 $pid
else
echo "No process found on port $1."
fi
}
# Quick HTTP server in the current directory (Python 3)
serve() {
local port="${1:-8000}"
echo "Serving current directory on http://localhost:${port}"
python3 -m http.server "$port"
}
# Pretty-prints each directory in your $PATH on a new line for easy inspection.
path() {
echo "$PATH" | tr ':' '\n'
}
# Side-by-side file comparison using your default Neovim alias.
vdiff() {
if [[ $# -lt 2 ]]; then echo "Usage: vdiff <file1> <file2>"; return 1; fi
nvim -d "$1" "$2"
}
# Previews the head of gzipped or zstd-compressed text/log files without extraction.
vgz() {
if [[ -z "$1" ]]; then echo "Usage: vgz <file.gz|file.zst> [lines]"; return 1; fi
local lines="${2:-20}"
case "$1" in
*.gz) gzip -dc "$1" | head -n "$lines" ;;
*.zst) zstdcat "$1" | head -n "$lines" ;;
*) echo "Unsupported archive format." ;;
esac
}
# Lists files inside .zip, .tar, .tar.gz, or .whl archives without extracting.
vzip() {
if [[ -z "$1" ]]; then echo "Usage: vzip <archive_file>"; return 1; fi
case "$1" in
*.zip|*.whl) unzip -l "$1" ;;
*.tar.gz|*.tgz) tar -tzvf "$1" ;;
*.tar) tar -tvf "$1" ;;
*) echo "Unsupported extension." ;;
esac
}Zsh Plugins
On top of the aliases above, which work in both Zsh and Bash, I add three plugins to my Zsh shell:
| Plugin | What it does |
|---|---|
zsh-autosuggestions |
Suggests commands as you type, based on your history — accept with → |
zsh-syntax-highlighting |
Highlights commands as you type, so typos and invalid commands stand out before you hit Enter |
fast-syntax-highlighting |
A faster, richer highlighter with more detailed colouring of paths, options, and strings |
No plugin manager is needed — clone them once:
mkdir -p ~/.zsh/plugins
git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/plugins/zsh-autosuggestions
git clone https://github.com/zsh-users/zsh-syntax-highlighting ~/.zsh/plugins/zsh-syntax-highlighting
git clone https://github.com/zdharma-continuum/fast-syntax-highlighting ~/.zsh/plugins/fast-syntax-highlightingThen source them at the end of ~/.zshrc — the highlighters wrap the line editor’s widgets, so they need to load after everything else:
source ~/.zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
source ~/.zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
source ~/.zsh/plugins/fast-syntax-highlighting/fast-syntax-highlighting.plugin.zshTo update them later, run git -C <plugin_dir> pull for each one.
The prompt itself is drawn by Oh My Posh, a fast, shell-agnostic prompt engine. I use it with Zsh, but the exact same config works in Bash (and Fish, Xonsh, PowerShell, and others) — only the one init line differs.
1. Install Oh My Posh. The official script drops a single binary into ~/.local/bin:
curl -s https://ohmyposh.dev/install.sh | bash -s2. Install a Nerd Font. The prompt uses powerline arrows and icons, which need a Nerd Font. Oh My Posh can install one for you — then select it in your terminal emulator’s settings:
oh-my-posh font install meslo3. Put the config in place. I keep my theme at ~/.config/ohmyposh/jandedobbeleer.omp.json:
mkdir -p ~/.config/ohmyposh
# then save the JSON below as ~/.config/ohmyposh/jandedobbeleer.omp.json4. Initialise it in your shell. Add one line to your shell config — after any PATH setup, but before the Zsh plugins, which should stay last:
# ~/.zshrc
eval "$(oh-my-posh init zsh --config ~/.config/ohmyposh/jandedobbeleer.omp.json)"
# ~/.bashrc
eval "$(oh-my-posh init bash --config ~/.config/ohmyposh/jandedobbeleer.omp.json)"Open a new terminal (or source the file) and the prompt appears.
What the prompt shows. The config is based on the jandedobbeleer theme that ships with Oh My Posh. Most segments only appear when they are relevant:
- Left side:
- User and path: your username, then the current directory.
- Git: the branch, ahead/behind status, and counts of changed, staged and stashed files. The segment changes colour depending on the repository’s state: clean, dirty, ahead/behind, or diverged.
- Language versions: Node, Go, Julia, Python, Ruby, and Azure Functions. Each one appears only inside a project for that language. For example, the Python version only shows in a folder that contains Python files.
- Cloud and privileges: the active AWS profile and region, and a wrench icon when you are root.
- Last command: how long the previous command took, and its status, which turns from green to red when it fails.
- Right side: the shell name, the currently playing track in YouTube Music, battery level and charging state, and the time.
{
"$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json",
"blocks": [
{
"alignment": "left",
"segments": [
{
"background": "#c386f1",
"foreground": "#ffffff",
"leading_diamond": "",
"style": "diamond",
"template": " {{ .UserName }} ",
"trailing_diamond": "",
"type": "session"
},
{
"background": "#ff479c",
"foreground": "#ffffff",
"powerline_symbol": "",
"options": {
"folder_separator_icon": " ",
"home_icon": "~",
"style": "full"
},
"style": "powerline",
"template": " {{ .Path }} ",
"type": "path"
},
{
"background": "#fffb38",
"background_templates": [
"{{ if or (.Working.Changed) (.Staging.Changed) }}#FF9248{{ end }}",
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}#ff4500{{ end }}",
"{{ if gt .Ahead 0 }}#B388FF{{ end }}",
"{{ if gt .Behind 0 }}#B388FF{{ end }}"
],
"foreground": "#193549",
"leading_diamond": "",
"powerline_symbol": "",
"options": {
"branch_template": "{{ trunc 25 .Branch }}"
},
"style": "powerline",
"template": " {{ .UpstreamIcon }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} {{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} {{ .StashCount }}{{ end }} ",
"trailing_diamond": "",
"type": "git"
},
{
"background": "#6CA35E",
"foreground": "#ffffff",
"powerline_symbol": "",
"style": "powerline",
"template": " {{ if .PackageManagerIcon }}{{ .PackageManagerIcon }} {{ end }}{{ .Full }} ",
"type": "node"
},
{
"background": "#8ED1F7",
"foreground": "#111111",
"powerline_symbol": "",
"style": "powerline",
"template": " {{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }} ",
"type": "go"
},
{
"background": "#4063D8",
"foreground": "#111111",
"powerline_symbol": "",
"style": "powerline",
"template": " {{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }} ",
"type": "julia"
},
{
"background": "#FFDE57",
"foreground": "#111111",
"powerline_symbol": "",
"options": {
"display_mode": "files",
"fetch_virtual_env": false
},
"style": "powerline",
"template": " {{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }} ",
"type": "python"
},
{
"background": "#AE1401",
"foreground": "#ffffff",
"powerline_symbol": "",
"options": {
"display_mode": "files"
},
"style": "powerline",
"template": " {{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }} ",
"type": "ruby"
},
{
"background": "#FEAC19",
"foreground": "#ffffff",
"powerline_symbol": "",
"options": {
"display_mode": "files"
},
"style": "powerline",
"template": " {{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }} ",
"type": "azfunc"
},
{
"background_templates": [
"{{if contains \"default\" .Profile}}#FFA400{{end}}",
"{{if contains \"jan\" .Profile}}#f1184c{{end}}"
],
"foreground": "#ffffff",
"powerline_symbol": "",
"options": {
"display_default": false
},
"style": "powerline",
"template": " {{ .Profile }}{{ if .Region }}@{{ .Region }}{{ end }} ",
"type": "aws"
},
{
"background": "#ffff66",
"foreground": "#111111",
"powerline_symbol": "",
"style": "powerline",
"template": " ",
"type": "root"
},
{
"background": "#83769c",
"foreground": "#ffffff",
"options": {
"always_enabled": true
},
"style": "plain",
"template": "<transparent></> {{ .FormattedMs }}⠀",
"type": "executiontime"
},
{
"background": "#00897b",
"background_templates": [
"{{ if gt .Code 0 }}#e91e63{{ end }}"
],
"foreground": "#ffffff",
"options": {
"always_enabled": true
},
"style": "diamond",
"template": "<parentBackground></> ",
"trailing_diamond": "",
"type": "status"
}
],
"type": "prompt"
},
{
"segments": [
{
"background": "#0077c2",
"foreground": "#ffffff",
"style": "plain",
"template": "<#0077c2,transparent></> {{ .Name }} <transparent,#0077c2></>",
"type": "shell"
},
{
"background": "#1BD760",
"foreground": "#111111",
"invert_powerline": true,
"powerline_symbol": "",
"options": {
"paused_icon": " ",
"playing_icon": " "
},
"style": "powerline",
"template": " {{ .Icon }}{{ if ne .Status \"stopped\" }}{{ .Artist }} - {{ .Track }}{{ end }} ",
"type": "ytm"
},
{
"background": "#f36943",
"background_templates": [
"{{if eq \"Charging\" .State.String}}#40c4ff{{end}}",
"{{if eq \"Discharging\" .State.String}}#ff5722{{end}}",
"{{if eq \"Full\" .State.String}}#4caf50{{end}}"
],
"foreground": "#ffffff",
"invert_powerline": true,
"powerline_symbol": "",
"options": {
"charged_icon": " ",
"charging_icon": " ",
"discharging_icon": " "
},
"style": "powerline",
"template": " {{ if not .Error }}{{ .Icon }}{{ .Percentage }}{{ end }}{{ .Error }} ",
"type": "battery"
},
{
"background": "#2e9599",
"foreground": "#111111",
"invert_powerline": true,
"leading_diamond": "",
"style": "diamond",
"template": " {{ .CurrentDate | date .Format }} ",
"trailing_diamond": "",
"type": "time"
}
],
"type": "rprompt"
}
],
"console_title_template": "{{ .Shell }} in {{ .Folder }}",
"final_space": true,
"version": 4
}For Neovim, I use AstroNvim as my configuration framework:
git clone --depth 1 https://github.com/AstroNvim/template ~/.config/nvim \
&& rm -rf ~/.config/nvim/.git \
&& nvimOn top of AstroNvim, I add two plugins:
- oil.nvim: edit your filesystem like a normal buffer. Press
-to open the current file’s folder, then rename, move, create, or delete files by editing the text. Save with:wand the changes are applied. - mini.nvim: a collection of small, independent modules that you enable one by one. AstroNvim already covers several of them, so I only enable the ones it lacks:
mini.ai, for smarter text objects such as “inside function argument”, andmini.surround, to add, change, or delete surrounding quotes and brackets.
AstroNvim loads every file in ~/.config/nvim/lua/plugins/, so I put both plugins in a file of their own, ~/.config/nvim/lua/plugins/extras.lua:
return {
{
"stevearc/oil.nvim",
lazy = false,
opts = {},
keys = {
{ "-", "<cmd>Oil<cr>", desc = "Open parent directory" },
},
},
{
"nvim-mini/mini.nvim",
version = false,
config = function()
require("mini.ai").setup()
require("mini.surround").setup()
end,
},
}The next time you open nvim, lazy.nvim installs both of them automatically.
I use Claude Code daily, and a handful of plugins make it noticeably better. Plugins are installed from inside a Claude Code session with the /plugin command. Everything below comes from Anthropic’s official marketplace, which you can add once if it is not already there:
/plugin marketplace add anthropics/claude-plugins-official
| Plugin | What it does |
|---|---|
code-review |
Reviews pull requests with several specialised agents, using confidence scoring to filter out false positives |
code-simplifier |
An agent that tidies recently modified code for clarity, consistency and maintainability without changing its behaviour |
context7 |
Connects Claude to Upstash’s Context7 server, which pulls current, version-specific library documentation and examples into context, so it stops guessing at APIs that have changed |
pyright-lsp |
Gives Claude the Pyright language server for Python, so it sees real type errors and can jump to definitions instead of guessing |
skill-creator |
Helps create new skills, improve existing ones, and benchmark them with evals |
superpowers |
A library of workflow skills: brainstorming before coding, test-driven development, systematic debugging, and subagent-driven development with built-in review |
Each one installs with a single command:
/plugin install code-review@claude-plugins-official
/plugin install code-simplifier@claude-plugins-official
/plugin install context7@claude-plugins-official
/plugin install pyright-lsp@claude-plugins-official
/plugin install skill-creator@claude-plugins-official
/plugin install superpowers@claude-plugins-official
Two notes:
pyright-lsponly connects Claude to the language server; the server itself must be on yourPATH. Install it withpixi global install pyright(ornpm install -g pyright).context7works anonymously out of the box. Set aCONTEXT7_API_KEYenvironment variable if you hit its rate limits.
SLURM Helpers for Supercomputers
This part is for the HPC crowd. If you have never touched a SLURM cluster, feel free to skip ahead to the conclusion — nothing below will be relevant to you.
For HPC environments running SLURM, I keep these functions in my shell configuration. They make interactive GPU sessions much less tedious to manage — particularly on Isambard-AI, where I spend a lot of time.
export NODE_SHELL="zsh"
getgnode() {
local ngpus=1
local time_limit="02:00:00"
local partition="workq" # Adjust for your cluster
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
echo "Usage: getgnode [OPTIONS]"
echo "Allocate and attach to an interactive SLURM GPU node."
echo ""
echo "Options:"
echo " -g, --gpus INT Number of GPUs to request (default: 1)"
echo " -p, --partition NAME SLURM partition (default: workq)"
echo " -t, --time HH:MM:SS Time limit (default: 02:00:00)"
echo " -h, --help Show this help"
return 0
;;
-g|--gpus) ngpus="$2"; shift 2 ;;
-p|--partition) partition="$2"; shift 2 ;;
-t|--time) time_limit="$2"; shift 2 ;;
*)
echo "Error: Unknown argument '$1'"
echo "Type 'getgnode --help' for usage."
return 1
;;
esac
done
salloc --partition="${partition}" \
--gres=gpu:"${ngpus}" \
--time="${time_limit}" \
srun --pty "${NODE_SHELL:-zsh}"
}
fetchgnode() {
if [[ "$1" == "-h" || "$1" == "--help" || -z "$1" ]]; then
echo "Usage: fetchgnode <jobid>"
echo "Attach a new shell to an existing SLURM job."
return 0
fi
srun --jobid="$1" --overlap --pty "${NODE_SHELL:-zsh}"
}
slog() {
if [[ -z "$1" ]]; then
echo "Usage: slog <job_id> [lines]"
echo "Tails the stdout log file for a given Slurm job."
return 1
fi
local lines="${2:-50}"
# Search current dir for slurm-<jobid>.out or <name>-<jobid>.out
local logfile=$(ls -t *"$1"*.out 2>/dev/null | head -n 1)
if [[ -n "$logfile" ]]; then
echo "Tailing log: $logfile"
tail -n "$lines" -f "$logfile"
else
echo "Error: No log file matching *'$1'*.out found in current directory."
return 1
fi
}
sfree() {
echo "=== Available Cluster Resources ==="
sinfo -N -h -o "%15P %10t %10G %N" | grep -E "idle|mix" | grep -v "drain" | column -t
}
snode() {
if [[ -z "$1" ]]; then
echo "Usage: snode <job_id>"
echo "Lookup the host node of a running job and attach to it."
return 1
fi
local node=$(squeue -j "$1" -h -o "%N" 2>/dev/null)
if [[ -z "$node" || "$node" == "N/A" ]]; then
echo "Error: Job $1 is not actively running."
return 1
fi
echo "Connecting to job $1 on node [$node]..."
srun --jobid="$1" --overlap --pty "${NODE_SHELL:-zsh}"
}
seffme() {
if [[ -z "$1" ]]; then
echo "Usage: seffme <job_id>"
echo "Check peak memory, CPU efficiency, and exit codes for a job."
return 1
fi
if command -v seff &>/dev/null; then
seff "$1"
else
sacct -j "$1" --format=JobID,JobName%20,State,ExitCode,MaxRSS,ReqMem,AllocCPUS,Elapsed -X
fi
}
rebatch() {
if [[ $# -lt 2 ]]; then
echo "Usage: rebatch <job_id> <script.sh>"
echo "Cancels <job_id> and submits <script.sh>."
return 1
fi
echo "Canceling job $1..."
scancel "$1"
echo "Submitting $2..."
sbatch "$2"
}
alias sqm="squeue --me"What Actually Stuck
Looking back, the journey was never really about finding the right distribution — though it took me a long time to admit that. Every distribution I used taught me something: Debian gave me discipline, Arch gave me depth, CentOS gave me an appreciation for stability, Nobara reminded me why community matters, Fedora showed me how polished a Linux desktop can be, and openSUSE Tumbleweed showed me that rolling release and reliability are not opposites.
What I was actually searching for was a coherent philosophy: a system I could understand completely, trust in production, enjoy on the desktop, and extend without fighting it. The distribution itself turned out to be the least important variable. openSUSE Tumbleweed with KDE is where I am happiest on the desktop — stable, community-driven, and free of version upgrades. AlmaLinux is where most of my containers and HPC environments live, and it has earned a place on some of my desktops too. And across every single one of them, Flatpak handles GUI applications and pixi handles everything else — Python environments, binary tools, cross-platform reproducibility — with a single unified interface that works identically whether I am on my workstation, my laptop, or a GH200 node on Isambard-AI.
The packages problem that once felt like an unsolvable compromise turned out to have a clean answer. I just had to wait for the Rust ecosystem to mature enough to build it — and stop insisting that the answer had to come bundled with a single distribution.