dotfiles are your virtual home... this is my home
| My choice | |
|---|---|
| OS | CachyOS (Arch-based) + WSL2 on Windows 11 |
| Desktop | Wayland: sway + Noctalia on quark · niri on astrobit · none on adam (WSL2) |
| Shell | bash |
| Terminal | foot (Wayland) · Windows Terminal (WSL2) |
| Editor | vim |
| Theme | Solarized dark (everywhere) |
A dotfiles repository is not intended to be forked — it is a personal backup and a source of inspiration. Feel free to take what is useful; sharing configs is great.
- Dependencies
- Bootstrap
- Directory structure
- bash
- vim
- Plugin set
- Language Server Protocol (LSP)
- Linting and autofix (ALE)
- Completion workflow
- Git (fugitive + gitgutter)
- Fuzzy finders (fzf.vim)
- Navigation (easymotion)
- Text manipulation (surround, commentary, unimpaired, repeat)
- Alignment (tabular, easy-align, maketable)
- Incrementing columns (VisIncr)
- Auto-pairs (lexima)
- Buffer and file navigation (dirvish, bbye, tagbar)
- Folding (FoldFocus, native markers)
- LaTeX (vimtex)
- Markdown (markdown-preview)
- Encryption (gnupg)
- Arithmetic (HowMuch)
- Per-filetype configuration
- Key mappings reference
- git
- modules — HPC environments
- claude
- Layout
- Global configuration (CLAUDE.md, settings.json)
- Status line
- Slash commands
- Skills — three-class machinery (skills-apply)
- Claude CLI wrappers — overview
- Cloud: Anthropic
- Cloud: OpenRouter
- Cloud: Z.ai
- Cloud: NVIDIA (build.nvidia.com)
- Local: three backends (ollama, llama.cpp, ik_llama.cpp)
- Local server management (llm-local-server)
- Ollama utilities
- llama.cpp utilities
- Machine-specific overrides
- Environment reference
- scripts
- desktop — quark's sway + Noctalia session
- desktop-astrobit — astrobit's niri session
- Extending the dotfiles
- Copyrights
| Tool | Purpose | Arch | Ubuntu / WSL2 |
|---|---|---|---|
| git | Version control | pacman -S git |
apt install git |
| bash ≥ 4.4 | Shell (uses autocd, globstar, histappend) |
pre-installed | apt install bash |
| vim | Editor | pacman -S vim |
apt install vim |
| GNU Stow | Symlink manager — deploys packages | pacman -S stow |
apt install stow |
| Lmod | Environment module system | yay -S lmod (AUR) |
apt install lmod |
| dircolors | Colour-coded ls output (reads ~/.dircolors.256dark) |
coreutils (pre-installed) | coreutils (pre-installed) |
| Tool | Purpose | Arch | Ubuntu / WSL2 |
|---|---|---|---|
| fzf | Fuzzy finder — used by vim (fzf.vim) and shell |
pacman -S fzf |
apt install fzf |
| rsync | synccp / syncmv shell functions |
pacman -S rsync |
apt install rsync |
| bc | Arithmetic in shell functions | pacman -S bc |
apt install bc |
| lesspipe | Rich pager for binary files | pacman -S lesspipe |
apt install lesspipe |
| python3 | Claude Code status line, helper scripts, NVIDIA proxy venv (needs python3-venv on Debian/Ubuntu) |
pacman -S python |
apt install python3 python3-venv |
| curl | Ollama / proxy health checks in claude_code helpers |
pre-installed | apt install curl |
Not needed on adam (WSL2, no desktop session). Not used on astrobit either: that
machine runs niri, not sway, so it stows desktop-astrobit instead — niri
plus a standalone foot config, with no Noctalia templates enabled.
| Tool | Purpose | Arch / CachyOS |
|---|---|---|
| niri | Wayland compositor — astrobit only | pacman -S niri |
| Tool | Purpose | Arch / CachyOS |
|---|---|---|
| sway | Wayland compositor | pacman -S sway |
| noctalia | Desktop shell — bar, launcher, notifications, lock screen; renders the colour palette into other apps' configs | AUR / upstream |
| foot | Terminal ($term) |
pacman -S foot |
| adw-gtk-theme | GTK theme the Noctalia hook selects — note the package is adw-gtk-theme, not adw-gtk3, which does not exist in the repos |
pacman -S adw-gtk-theme |
| qt6ct | Qt6 platform theme — reads the generated palette. qt5ct is deliberately not installed: there is no qt5-base on this machine |
pacman -S qt6ct |
| btop | System monitor, themed via template | pacman -S btop |
| grim / slurp | Screenshots (Print, and region capture) |
pacman -S grim slurp |
These are not packaged in distro repos. Install to the paths expected by each
modulefile (see modules/.modules/<name>/<version>.lua for the exact root).
| Toolchain | Source |
|---|---|
| NVIDIA HPC SDK | developer.nvidia.com/hpc-sdk |
| Intel oneAPI Base + HPC Toolkit | intel.com/oneapi |
| AMD AOCC | developer.amd.com/amd-aocc |
| OpenMPI | open-mpi.org — build from source against the desired compiler |
| Tool | Purpose | Arch | Ubuntu / WSL2 |
|---|---|---|---|
| Ollama | Local LLM server for claude-local |
yay -S ollama (AUR) |
curl -fsSL https://ollama.com/install.sh | sh |
| Claude Code | AI coding assistant (claude CLI) |
npm install -g @anthropic-ai/claude-code |
same |
| nvm | Node.js version manager | github.com/nvm-sh/nvm | same |
| pnpm | Fast Node package manager | npm install -g pnpm |
same |
| texlive + latexmk | LaTeX compilation | pacman -S texlive-core latexmk |
apt install texlive latexmk |
| ImageMagick | Image conversion scripts in scripts/.scripts/ |
pacman -S imagemagick |
apt install imagemagick |
| ffmpeg | Video-to-GIF and media helpers | pacman -S ffmpeg |
apt install ffmpeg |
| exiftool | EXIF metadata in image scripts | pacman -S perl-image-exiftool |
apt install libimage-exiftool-perl |
| borg | Automated backup (scripts/.scripts/borg-automated-backup.sh) |
pacman -S borg |
apt install borgbackup |
| nvidia-smi | GPU status in ollama-status |
part of NVIDIA driver | part of NVIDIA driver |
Deployment is handled by dotify.sh, a custom symlink script.
It creates ~/.bash/, ~/.vim/, etc. and populates them with symlinks
pointing back into this repository.
# 1. Clone the repository
git clone https://github.com/szaghi/dotfiles ~/dotfiles
# 2. (optional) copy your private bash file
cp your-private-stuff ~/dotfiles/bash/private
# 3. Run the deploy script
bash ~/dotfiles/dotify.shdotify.sh is idempotent — re-running it is safe; it overwrites symlinks with ln -fs.
None. bd (back-directory navigation) used to be a submodule and is now
vendored at scripts/.scripts/bd, so a plain git clone is complete — there
is nothing to initialise.
Every top-level directory is a stow package whose internal layout mirrors
$HOME, except machines/ (host profiles) and the two asset directories.
dotfiles/
├── bash/ shell configuration — common to every host
├── bash-adam/ adam's overrides (signing key, local LLM settings)
├── bash-astrobit/ astrobit's overrides
├── bash-quark/ quark's overrides
├── claude/ Claude Code configuration, skills and commands
├── desktop/ quark's Wayland desktop (sway + Noctalia + foot + Qt)
├── desktop-astrobit/ astrobit's Wayland desktop (niri + foot)
├── git/ git configuration and commit template
├── machines/ per-host package and skill lists — not a stow package
├── miscellanea/ single-file configs (latexmkrc, NAS mount script)
├── modules/ Lmod modulefiles for HPC toolchains
├── python/ Python env (pythonrc, pylintrc)
├── scripts/ scripts, user systemd units and standalone binaries
├── ssh/ ssh client configuration (config only, never keys)
├── usr/ desktop application entries (.desktop files)
├── vim/ vim configuration and plugins
└── dotify.sh deploy script
bash/.bashrc loads modular files from ~/.bash/:
| File | Purpose |
|---|---|
aliases |
Command shortcuts (ll, bd, pacman helpers, LaTeX, git push aliases) |
exports |
Environment variables |
paths |
PATH additions |
functions |
Shell utility functions |
compilers |
Compiler flags and module helpers |
optprogs |
Optional program configuration |
prompt |
Two-line bash prompt with git status integration |
claude_code |
Dual-mode Claude Code setup (local Ollama + cloud Anthropic) |
private |
Machine-specific secrets — not tracked |
The prompt is two-line and git-aware: it shows branch, dirty state, and ahead/behind counts.
Vim is the editor for everything I do — Fortran/HPC, Python, LaTeX, prose.
The setup is a single .vimrc plus per-filetype ftplugins and per-plugin
configuration files, all managed through vim-plug.
Persistent undo, native relative line numbers, LSP-driven navigation and
completion for Fortran/Python/LaTeX/bash, and ALE-powered linting round out
an IDE-grade experience that stays 100% Vim (no Neovim required).
- Leader:
,— all custom mappings are prefixed with,. - Localleader:
,— set beforeplug#beginso vimtex picks it up at load. - Persistent undo:
~/.vim/undo/(auto-created, excluded from stow/git). - Color scheme: Solarized dark with cursorline, 1-column foldcolumn,
signcolumn=yespinned (no gitgutter flicker).
Install / update plugins:
:PlugInstall " install missing plugins after pulling the repo
:PlugUpdate " update all plugins
:PlugClean! " remove plugins no longer listed in .vimrc| Category | Plugins |
|---|---|
| Appearance | vim-colors-solarized, lightline + lightline-bufferline, rainbow_parentheses |
| LSP & linting | yegappan/lsp, ALE |
| Git | vim-fugitive, vim-gitgutter |
| Fuzzy finders | fzf + fzf.vim |
| Navigation | easymotion, vim-dirvish, tagbar, vim-bbye, vim-foldfocus |
| Text editing | vim-surround, vim-repeat, vim-commentary, vim-unimpaired, lexima, VisIncr |
| Alignment | tabular, vim-easy-align, vim-maketable |
| Languages | vimtex, markdown-preview.nvim, python-syntax |
| Utilities | vim-gnupg, HowMuch, plugconf |
Per-plugin config lives in vim/.vim/plugconf/*.vim (loaded by plugconf
after plug#end()).
Provided by yegappan/lsp, a pure-Vim9 LSP client (no Neovim, no Node). Configured servers:
| Language | Server | Install |
|---|---|---|
| Fortran | fortls | pipx install fortls |
| Python | basedpyright | pipx install basedpyright |
| LaTeX | texlab | GitHub release (Ubuntu 24.04 has no apt package) |
| Bash | bash-language-server | npm install -g bash-language-server |
One-shot install for all four plus shellcheck:
~/.scripts/install-vim-lsp.shThe script is idempotent and detects missing binaries, so it is safe to re-run after a distro upgrade or on a fresh machine.
Key mappings (active only in buffers with an attached server):
| Key | Action |
|---|---|
gd |
:LspGotoDefinition |
gr |
:LspShowReferences (populates quickfix) |
K |
:LspHover — docs in a floating popup under the cursor (dismissed on motion) |
<leader>rn |
:LspRename — rename symbol across project |
<leader>la |
:LspCodeAction — quick-fixes and refactors |
<leader>lf |
:LspFormat — format buffer via LSP |
[d / ]d |
Previous / next diagnostic |
<Tab> |
Trigger completion (see below) |
Fortran note. fortls diagnostics are intentionally disabled via
features: { diagnostics: v:false }. fortls's CLI --excl_paths only
accepts absolute paths, so on FoBiS-managed
repos with third_party/*/docs/api/src/ shadow trees it emits spurious
diagnostics for any symbol coming from a shadow-duplicated module.
Navigation, hover, completion and references still work perfectly —
only the red-E> diagnostic signs are suppressed. Projects that actually
want diagnostics can drop a per-project .fortls and flip the feature
back on.
ALE handles linting and fixing
without running its own LSP client (g:ale_disable_lsp = 1 — LSP ownership
stays with yegappan/lsp).
| Filetype | Linter | Fixer |
|---|---|---|
| Python | ruff | ruff + ruff_format on save |
| Bash / sh | shellcheck | — |
| Any | — | trim_whitespace, remove_trailing_lines |
- Python files are automatically formatted with ruff on
:w. Other filetypes only lint — no surprise autofix. - Diagnostic popup under the cursor:
:ALEDetail. - Gutter signs:
✗(error),!(warning).
Completion is manual, not auto-popup (to keep Vim feeling like Vim). The popup menu is triggered from LSP's omnifunc:
| Key (insert mode) | Behavior |
|---|---|
<Tab> |
If popup visible → next item; after a word char → trigger omnicomplete; otherwise → literal <Tab> |
<S-Tab> |
If popup visible → previous item; otherwise → literal <S-Tab> |
<CR> |
If popup visible → accept selected item; otherwise → delegate to lexima (auto-pair Enter) |
<C-x><C-o> |
Manual omnicomplete trigger |
The <Tab> behavior is context-aware: it will not eat indentation
whitespace at the start of a line.
vim-fugitive provides the git
porcelain inside Vim. vim-gitgutter
shows per-hunk change indicators in the signcolumn.
| Key | Action |
|---|---|
<leader>gs |
:Git — status window (stage/unstage with s/u, diff with =) |
<leader>gb |
:Git blame |
<leader>gd |
:Gdiffsplit — 3-way diff in a split |
<leader>gl |
:Git log --oneline --decorate --all |
<leader>gc |
:Git commit |
<leader>gp |
:Git push |
Example — stage a hunk, commit, push from inside Vim without leaving the buffer:
,gs " open status
→ press s on the file to stage
→ press cc to write the commit message
→ :wq to commit
,gp " push
signcolumn=yes is pinned in .vimrc so the gutter never flickers as
gitgutter adds or removes signs.
Requires fzf and ripgrep
on $PATH.
| Key | Action |
|---|---|
<leader>f |
:Files — fuzzy file finder in cwd |
<leader>b |
:Buffers — fuzzy buffer switcher |
<leader>r |
:Rg — ripgrep (type pattern, <CR> to run) |
<leader>t |
:Tags — ctags jump |
<leader>h |
:History — recently-opened files |
<leader>/ |
:Lines — fuzzy search across buffer lines |
easymotion gives 2-keystroke
jumps to anywhere visible. Default aggressive mappings are disabled
(g:EasyMotion_do_mapping = 0) so single-, mappings stay untouched.
| Key | Action |
|---|---|
,,s{char} |
Jump to any occurrence of {char} on screen |
,,w |
Jump to the start of a word |
,,j / ,,k |
Jump down / up to a target line |
,,l / ,,h |
Jump forward / backward within the current line |
Arrow keys for window splits:
| Key | Action |
|---|---|
<A-Up> / <A-Down> / <A-Left> / <A-Right> |
Focus window above / below / left / right |
<C-Right> / <C-Left> |
Next / previous buffer (bnext / bprevious) |
qq |
Close buffer (keeps window layout, via vim-bbye's :Bdelete) |
<C-N> |
Toggle relativenumber on current window |
<leader>v |
Toggle virtualedit=all (cursor allowed in empty columns) |
Four tpope plugins that become muscle memory within days.
vim-surround — manipulate surrounding delimiters:
| Command | Effect |
|---|---|
cs"' |
Change surrounding " to ': "foo" → 'foo' |
ds( |
Delete surrounding (): (foo) → foo |
ysiw] |
Wrap word in []: foo → [foo] |
yss) |
Wrap whole line in () |
S< (visual) |
Wrap selection in <> |
vim-commentary — toggle comments:
| Command | Effect |
|---|---|
gcc |
Toggle comment on current line |
gc{motion} |
Toggle comment over motion (e.g. gcap — around paragraph) |
gc (visual) |
Toggle comment on selection |
vim-unimpaired — paired bracket mappings:
| Command | Effect |
|---|---|
]q / [q |
Next / previous quickfix entry |
]l / [l |
Next / previous location list entry |
]b / [b |
Next / previous buffer |
]<Space> / [<Space> |
Add blank line below / above |
]e / [e |
Swap current line with next / previous |
yo{char} |
Toggle option: yos (spell), yow (wrap), yoh (hlsearch), yol (list), yon (number)… |
vim-repeat — lets . repeat
complex plugin actions (surround, commentary, unimpaired). No mappings;
just works.
Three overlapping plugins, used for different jobs.
vim-easy-align — interactive visual alignment (fastest for one-off work):
" select a visual block, then:
ga= " align on first =
ga*= " align on every =
ga<CR>, " align on , with centered spacing
tabular — scriptable alignment for macros and autocmds:
:Tabularize /=
:Tabularize /|vim-maketable — convert a CSV/TSV selection to a markdown/rst/org table. Useful when pasting data into prose.
VisIncr — fills a visual-block
column with an arithmetic / alphabetical / date / power sequence. More
expressive than Vim's native g<C-a> / g<C-x>.
" visually select a column, then:
:I " increment: 1 2 3 4 ...
:II " decrement
:IA " alphabetical: a b c d ...
:IYMD " dates: 2026-04-24 2026-04-25 ...
:IPOW2 " powers: 1 2 4 8 16 32 ...
Loaded lazily — vim-plug only sources it when one of the I* commands
is invoked.
lexima.vim auto-closes (), [],
{}, "", '' and friends. Smart enough to avoid duplicating the closer
when the cursor is already at it. <CR> inside an empty pair opens a new
indented block:
if foo| — press <CR> → if foo
| |
end
The completion config above wires lexima's <CR> expansion into the
completion-<CR> fallback, so both behaviors coexist.
vim-dirvish — directory browser.
Replaces netrw. :Dirvish or - (dash) opens the current file's directory
as a plain, sortable buffer.
- " open parent dir in dirvish
R " rename with native vim commands on the buffer
:x " filter (e.g. :sort)
Directory-as-buffer model — you :w to persist changes, like a normal file.
vim-bbye — :Bdelete closes a buffer
without destroying the window layout. Mapped to qq.
tagbar — ctags-driven symbol outline. Sidebar on the left, sorted:
| Key | Action |
|---|---|
<F3> |
Toggle tagbar |
Requires ctags (apt install exuberant-ctags or universal-ctags).
For Fortran/Python/LaTeX the LSP symbol lookup (gd, gr) is usually
more accurate; tagbar is the fallback for filetypes without an LSP.
.vimrc sets foldmethod=marker globally so {{{ / }}} triple-brace
markers define folds. Fold column shows as a 1-col gutter.
vim-foldfocus (Python
and Fortran only) opens the current fold in an isolated split for
distraction-free editing:
| Key | Action |
|---|---|
<C-f> |
Open current fold in a vertical split (FoldFocus) |
za |
Native: toggle fold under cursor |
zR / zM |
Native: open all / close all folds |
foldlevelstart=0 — files open fully folded. foldopen is tuned so
most navigation commands auto-open folds as needed.
A dedicated autocmd (augroup folding) temporarily sets foldmethod=manual
during insert mode to prevent fold recalculation on every keystroke in
large Fortran files.
vimtex is loaded for *.tex files.
Compilation uses latexmk (configured globally in ~/.latexmkrc, see
miscellanea/.latexmkrc). PDF viewer: Evince.
Key (localleader = ,) |
Action |
|---|---|
,ll |
Start continuous compilation (:VimtexCompile) |
,lv |
Forward search to PDF viewer (:VimtexView) |
,lc |
Clean aux files |
,le |
Show compilation errors in quickfix |
,li |
Show compilation info |
,lt |
Toggle table of contents |
The localleader = "," is set in .vimrc before plug#begin — setting
it later (e.g. in an ftplugin) is too late, vimtex registers its mappings
at plugin-load time.
markdown-preview.nvim
renders markdown live in the browser. Works in plain Vim despite the .nvim
suffix.
:MarkdownPreview " open preview in default browser
:MarkdownPreviewStop " stop the preview server
Dark theme by default (g:mkdp_theme = 'dark'); see vim/.vim/plugconf/markdown-preview.vim
for all tunables (TOC, KaTeX math, Mermaid, PlantUML, custom CSS).
vim-gnupg transparently
encrypts/decrypts files matching *.gpg, *.pgp, *.asc. Opening a
.gpg file prompts for the passphrase; saving re-encrypts in place.
vim secrets.txt.gpg
" edit normally
:w
" → file is re-encrypted to disk
GPG agent handles passphrase caching.
HowMuch evaluates an arithmetic
expression in the current selection and appends the result. Loaded
lazily via :HowMuch.
" visually select: 2 * (3 + 4)
:HowMuch
" → appends: = 14
Per-filetype settings live in vim/.vim/ftplugin/<filetype>.vim and are
auto-sourced by Vim when a buffer of that type is opened:
| File | Purpose |
|---|---|
fortran.vim |
Per-buffer free/fixed source-form detection (b:fortran_fixed_source), textwidth per form, syntax folding |
python.vim |
4-space indent (overrides the global 3), indent-based folding, python-syntax highlight knobs |
tex.vim |
English spell check, synmaxcol=0 for long equations, vimtex viewer set to Evince |
A custom filetype detection file vim/.vim/ftdetect/fobos.vim treats
any file whose name starts with fobos as dosini (so
FoBiS project files get syntax
highlighting).
Common programming-file behavior (whitespace trimming, blank-line squash,
RainbowParentheses activation, syntax folding) is defined in a single
augroup programming block in .vimrc.
Leader = ,, Localleader = ,. Only custom mappings are listed — native
Vim keys still behave as usual.
Editing / windows
| Key | Mode | Action |
|---|---|---|
/ |
n, x | Search with Perl/Python regex semantics (\v) |
Y |
n | Yank to end of line (analogous to D) |
<NL> (Ctrl-J) |
n | Insert line break at cursor (opposite of J) |
v |
x | Cycle through visual → visual-block → visual-line |
<C-e> / <C-y> |
n | Scroll viewport 2 lines down / up |
<A-↑↓←→> |
n | Focus window above / below / left / right |
<F2> |
n | Toggle line wrap |
<C-N> |
n | Toggle relativenumber |
<leader>v |
n | Toggle virtualedit=all |
Buffers
| Key | Action |
|---|---|
<C-Right> / <C-Left> |
:bnext / :bprevious |
]b / [b |
Same (via unimpaired) |
qq |
:Bdelete (vim-bbye) |
Finders (fzf.vim) — see Fuzzy finders. LSP — see LSP. Git — see Git. Easymotion — see Navigation.
Plugin togglers
| Key | Action |
|---|---|
<F3> |
Toggle tagbar |
<C-f> |
FoldFocus — current fold in vsplit (Python/Fortran only) |
| File | Deployed to | Purpose |
|---|---|---|
git/.gitconfig |
~/.gitconfig |
Git identity, aliases, GPG signing |
git/.git-templates/git_commit_message_template |
~/.git-templates/git_commit_message_template |
Conventional Commits template |
git/.git-templates/hooks/post-commit |
~/.git-templates/hooks/post-commit |
Post-commit hook shipped with the template directory |
Commits follow Conventional Commits:
type(scope): description
commit.gpgsign = true is set repo-wide in git/.gitconfig; the key itself is
per machine, declared in bash-<machine>/.gitconfig.local (symlinked to
~/.gitconfig.local). adam, quark and astrobit therefore carry three different
keys — a key is tied to the machine that holds its secret half, not to the
identity. All three are uploaded to the same GitHub account; GitHub accepts any
number of GPG keys and verifies a commit against whichever one signed it.
Two pieces have to be present or signing fails:
-
The secret key. A fresh machine has an empty keyring, and
git commitfails withgpg: skipped "<id>": No secret key. Either import the existing key or generate a new one for that machine and update its.gitconfig.local:gpg --quick-generate-key "Stefano Zaghi <stefano.zaghi@gmail.com>" ed25519 sign never gpg --list-secret-keys --keyid-format=long # take the 16-hex id gpg --armor --export <id> # paste into GitHub → SSH and GPG keys
-
GPG_TTY. The agent needs to know which terminal to draw the pinentry prompt on; without it signing fails withInappropriate ioctl for device. Exported frombash/.bash/exports, with~/.gnupg/gpg-agent.confselectingpinentry-cursesso it also works over ssh and in a bare TTY.
The revocation certificate generated alongside the key
(~/.gnupg/openpgp-revocs.d/<fingerprint>.rev) is what revokes the key if it is
ever compromised. It is not in this repo and must not be — back it up somewhere
private and offline.
Lmod manages compiler toolchains.
Modulefiles live in modules/.modules/ (deployed to ~/.modules/ via stow).
Lmod is initialised automatically in bash/.bash/exports.
Daily usage:
module avail # list all available modules
module load gcc/15.1.0 # load GCC 15.1.0
module load nvhpc/24.11 # load NVIDIA HPC SDK 24.11
module load openmpi/5.0.7-gnu14.2.0 # load OpenMPI (built against GCC 14)
module list # show currently loaded modules
module unload gcc/15.1.0 # unload a single module
module purge # unload everythingThe bash prompt shows loaded modules automatically (env {gcc/15.1.0 openmpi/5.0.7-gnu14.2.0}).
Available modules:
| Module | Description |
|---|---|
gcc/15.1.0 |
GCC 15.1.0 compiler toolchain |
nvhpc/23.1 · 24.11 · 25 · 26 |
NVIDIA HPC SDK (nvfortran, nvc, nvc++) |
intel/oneapi |
Intel oneAPI compilers (ifort, icc, icx) |
amd/5.1.0 |
AMD AOCC 5.1.0 compilers |
openmpi/3.1.5-nvhpc{20.7,22.3,23.1} |
OpenMPI 3.1.5 built against NVHPC |
openmpi/4.1.4-gnu11.2.0 |
OpenMPI 4.1.4 built against GCC 11 |
openmpi/4.1.4-intel2021.5.0 |
OpenMPI 4.1.4 built against Intel 2021 |
openmpi/5.0.7-gnu14.2.0 |
OpenMPI 5.0.7 built against GCC 14 |
Adding a new module:
Modulefiles are Lua scripts. Create one at modules/.modules/<name>/<version>.lua
mirroring the existing ones. Minimal template:
whatis("Short one-line description")
help([[
Long description shown by `module help <name>/<version>`.
]])
local root = "/path/to/install"
family("compiler") -- optional: ensures only one compiler is active at a time
if not isDir(root) then
LmodError(root .. " not found")
end
prepend_path("PATH", pathJoin(root, "bin"))
prepend_path("LD_LIBRARY_PATH", pathJoin(root, "lib"))
prepend_path("MANPATH", pathJoin(root, "share/man"))
setenv("CC", "gcc")
setenv("CXX", "g++")
setenv("FC", "gfortran")Because ~/.modules is a stow symlink pointing into the repo, the new file is
immediately visible to Lmod — no re-stowing needed. Verify with module avail.
Configuration and wrappers for Claude Code, a CLI coding agent. The setup provides a single mental model over three cloud providers (Anthropic, OpenRouter, Z.ai) and three local inference backends (Ollama, llama.cpp, ik_llama.cpp), plus project-aware context, persona instructions and a custom Solarized status line.
Everything is driven by one command — claude-help — which prints the
live quick-reference. If a detail here ever drifts from reality, trust
claude-help and open a PR against this section.
claude/ is a stow package deployed to ~/.claude/:
| File | Purpose |
|---|---|
CLAUDE.md |
Global persona and project rules (loaded into every session) |
settings.json |
Permissions, model default, status-line binding, enabled plugins, marketplaces |
settings.local.json |
Machine-specific overrides — gitignored |
statusline-command.sh |
Custom status line (Python-backed JSON parser, Solarized palette) |
commands/ |
Custom slash commands — /semantic-commit, /capture-findings (see Slash commands) |
skills/ |
Custom user-authored skills (class A) + manifest.toml for third-party loose skills (class C) — see Skills |
The shell wrappers live in a separate package: bash/.bash/claude_code
(≈820 LOC, sourced from ~/.bashrc). Machine-specific knobs (GPU IDs,
binary paths, model defaults) go into ~/.bash/claude_code.local —
loaded automatically at the end of claude_code if present.
CLAUDE.md is the system-prompt overlay, loaded into every Claude
session. It encodes:
- Persona — analytic peer, not service assistant; unvarnished honesty; challenge premises; steel-man before dismantling; Socratic questioning on evaluative disagreements.
- Repo layout rules —
~/fortran/for Fortran,~/python/for Python; verify paths before using them. - Build system rules — FoBiS.py is primary; no make/cmake substitutions;
dependency management via
FoBiS.py fetch, not submodules. - Fortran conventions —
.F90uppercase,implicit noneeverywhere,allocatableoverpointer, column-major loop ordering, kind specifications viaiso_fortran_env,iso_c_bindingfor C interop, strictimplicit SAVEtrap guard, OpenMPdefault(none). - GPU/OpenACC rules —
parallel loopwith explicitgang/vector,!$acc declarediscipline, atomic-ops red-flag, reproducibility caveats. - Python conventions — Ruff patterns (B904 chained exceptions, B905
strict=on zip, RUF002 no Unicode math symbols), PEP 604 union syntax, NumPy-style docstrings for scientific code, Makefile as standard dev interface,.venvalways,git-clifffor changelogs. - Commit rules — Conventional Commits; never
Co-authored-bylines for AI; never auto-commit. Claude cannot sign: its shell has no TTY, so the pinentry prompt cannot be drawn — commits are written by hand.
settings.json configures the runtime:
| Key | Value | Purpose |
|---|---|---|
permissions.allow |
Read/Write/Edit on ~/fortran/** and ~/python/** |
Frictionless tool use inside my project trees |
model |
opus |
Default cloud model |
statusLine.command |
bash /home/stefano/.claude/statusline-command.sh |
Custom status line |
enabledPlugins |
frontend-design, skill-creator, cli-anything, document-skills |
Pre-enabled plugins |
extraKnownMarketplaces |
anthropic-agent-skills, cli-anything |
Extra plugin sources |
alwaysThinkingEnabled |
false |
Thinking is opt-in per request |
effortLevel |
high |
Max reasoning effort by default |
skipDangerousModePermissionPrompt |
true |
Skip the --dangerously-skip-permissions warning |
agentPushNotifEnabled |
true |
Push notifications when background agents finish |
statusline-command.sh reads the Claude Code statusLine JSON from stdin,
parses it with a one-shot inline python3 call, and renders a Solarized-dark
status line:
<model>·<GPUs>×GPU │ <cwd-basename> │ <branch>[<dirty>↑n↓m] │ ctx:N% │ 5h:N%(Xh) │ 7d:N%(Xd)
- Model — from the JSON
display_name. On local backends, appended with<N>×GPUwhenCLAUDE_LOCAL_GPUSis exported. - Git — branch,
+staged,!unstaged,?untracked,$stashed,↑N↓Mahead/behind upstream. - Context % — percentage of context window used. Colour-graded green → yellow → orange → red at 50/80/95%.
- Rate limits — 5-hour and 7-day usage percentages with countdown to reset. Same colour ladder.
Requires python3 (used once per render) and works on any terminal with
256-colour support.
Custom slash commands are plain markdown files under claude/.claude/commands/,
deployed by stow as symlinks into ~/.claude/commands/. Lifecycle is identical
to class-A custom skills — git tracks the source, stow deploys symlinks, no
installer or extra machinery.
| Command | Purpose |
|---|---|
/semantic-commit |
Analyze staged diff + recent commit style, emit a Conventional Commits-formatted message (no auto-commit, no Co-authored-by lines) |
/capture-findings |
Capture session findings into repo docs, repo CLAUDE.md / .claude/, and global ~/.claude/CLAUDE.md — auto-detects branch, merge-base, and existing repo Claude config |
Each command is a markdown file with a YAML front-matter declaring its
description and the allowed-tools (whitelisted Bash invocations plus
file ops). The body is the prompt template; lines beginning with !`
are shell substitutions evaluated at invocation time (see
Claude Code custom commands docs).
See commands/semantic-commit.md for a minimal template and
commands/capture-findings.md for a richer one with git-aware substitutions.
Add a new slash command:
# 1. write the command (front-matter + prompt body)
vim ~/dotfiles/claude/.claude/commands/my-command.md
# 2. version it
cd ~/dotfiles && git add claude/.claude/commands/my-command.md
# 3. deploy (idempotent — stow only adds the new symlink)
bash dotify.sh claudeThe new /my-command is immediately available in the next Claude Code
session on this host; other hosts pick it up on their next git pull && bash dotify.sh claude.
Skills in ~/.claude/skills/ fall into three structurally different classes
with three different lifecycle owners. The dotfiles wire all three into one
declarative, per-host workflow via skills-apply (scripts/.scripts/skills-apply,
≈340 LOC bash) plus a TOML manifest. The full design is in
claude/.claude/skills/README.md; the
summary:
| Class | Examples | Lifecycle owner | Where declared |
|---|---|---|---|
| A. Custom user-authored | fobis, research-lookup, markdown-mermaid-writing, markitdown, scientific-writing, generate-image |
git + stow | claude/.claude/skills/<name>/ (real source dirs) |
| B. Plugin / marketplace | frontend-design, skill-creator, cli-anything, document-skills |
claude plugin CLI |
settings.json → enabledPlugins |
| C. Third-party loose | perplexity-search |
upstream installers (pipx, venv, curl | bash…) |
claude/.claude/skills/manifest.toml |
Per-host filtering of class C: machines/<hostname>.skills (one skill name
per line, blanks and # comments allowed). A missing file means "install
every manifest entry on this host". Class A always stows everywhere
(source-only, cheap); class B is host-uniform via settings.json.
skills-apply interface:
skills-apply install # install everything declared (idempotent)
skills-apply update # update everything installed
skills-apply status # show installed-vs-declared for all three classes
skills-apply remove <name> # uninstall one skill (plugin or manifest)Restart Claude Code after install or update so plugin changes load.
Adding a new skill:
- Class A — drop the directory under
claude/.claude/skills/<name>/(at minimum aSKILL.md),git add, re-stow withbash dotify.sh claude. - Class B —
claude plugin install <name>@<marketplace>, then commit the resultingsettings.jsonchange. Future hosts pick it up viaskills-apply install. - Class C — add a
[skills.<name>]block tomanifest.tomlwithcheck/install/update/uninstallshell commands; add the name to eachmachines/<host>.skillsfile that should sync it.
The new dotfiles dependency this adds is just claude (the Claude Code
CLI binary) — already required as a Development extra. Python 3 (already
required) is reused for inline tomllib parsing of the manifest.
All wrappers in bash/.bash/claude_code call the real claude binary
with a curated set of env vars and CLI flags. None replace the binary —
they're small convenience functions.
Overall flow:
┌────────────────────┐
│ claude-help │ full quick-reference
└────────────────────┘
│
┌─────────────────────────────┼─────────────────────────────┐
│ │ │
CLOUD LOCAL UTILITIES
claude claude-local llm-local-server
claude-sonnet (ollama|llama|ikllama) ollama-{pull,create,…}
claude-opus llama-{models,alias,info}
claude-plan claude-nvidia nvidia-models
claude-openrouter (proxy backend)
claude-zai[-fast|-turbo|-premium]
claude-local auto-starts the requested backend and auto-stops any
other local backend currently running — only one of ollama/llama/ikllama
is live at a time. llm-local-server is the lower-level management verb.
claude-nvidia is the odd one out: it reaches a cloud catalog but is
built on the local machinery, because NVIDIA's hosted API needs a
translation proxy running on this host. See
Cloud: NVIDIA.
Uses your standard Anthropic subscription (no extra key file).
| Command | What it does |
|---|---|
claude |
Subscription default (model from settings.json → opus) |
claude-sonnet |
Force Sonnet (claude --model sonnet) |
claude-opus |
Force Opus (claude --model opus) |
claude-plan |
Read-only plan mode (Opus) → auto-switches to Sonnet on execute (--permission-mode plan --model opusplan) |
Routes through OpenRouter, giving access to 100+ models including free tiers.
claude-openrouter # default model
claude-openrouter google/gemma-3-27b-it:free
claude-openrouter anthropic/claude-3.5-sonnet- API key:
~/.openrouter-ai-key(gitignored) orOPENROUTER_API_KEYenv override - Endpoint:
https://openrouter.ai/api(the Anthropic SDK appends/v1/messages) - Default model:
$OPENROUTER_DEFAULT_MODEL(currentlyqwen/qwen3.6-plus-preview:free)
Routes through Z.ai's Anthropic-compatible endpoint for the GLM model family.
| Command | Model |
|---|---|
claude-zai [model] |
default: glm-5-turbo, or any GLM model by ID |
claude-zai-fast |
glm-4.5-air |
claude-zai-turbo |
glm-5-turbo |
claude-zai-premium |
glm-5.1 |
- API key:
~/.z-ai-key(gitignored) orZAI_API_KEYenv override - Endpoint:
https://api.z.ai/api/anthropic
Routes through NVIDIA's hosted model catalog at build.nvidia.com.
Status: working, but not in practical use. The harness is complete and verified end-to-end; the blocker is on NVIDIA's side. Read Latency before spending time here.
claude-nvidia # default model
claude-nvidia deepseek-ai/deepseek-v4-flash-0731 # explicit model
claude-nvidia --no-autostop # leave the proxy running
nvidia-models # list the served catalog
nvidia-models deepseek # filter it- API key:
~/.nvidia-api-key(gitignored) orNVIDIA_API_KEYenv override - Upstream:
https://integrate.api.nvidia.com - Default model:
$NVIDIA_DEFAULT_MODEL(currentlydeepseek-ai/deepseek-v4-flash-0731)
Why this one needs a proxy. OpenRouter and Z.ai are two-env-var
wrappers because their endpoints are already Anthropic-compatible.
NVIDIA's hosted catalog is not — it serves only the OpenAI API, and
/v1/messages returns 404 there. Claude Code speaks the Anthropic
Messages API, so a local translation proxy
(nvd-claude-nim) sits in
between:
claude ──Anthropic /v1/messages──▶ 127.0.0.1:8787 ──OpenAI /v1/chat/completions──▶ integrate.api.nvidia.com
(local proxy)
That proxy is a server with a lifecycle, so it is managed as a fourth
llm-local-server backend rather than as a bare wrapper. claude-nvidia
auto-starts it exactly as claude-local auto-starts ollama:
llm-local-server start --backend nvidia
llm-local-server status --backend nvidia
llm-local-server stop --backend nvidiaTwo deliberate asymmetries with the local backends:
- It does not evict other backends.
claude-localstops any other local engine to free VRAM. NVIDIA inference is remote and the proxy is a few MB of Python, so it coexists with a running ollama — stopping a local model to reach a cloud one would be pure loss. --gpusis meaningless and ignored: no inference happens on this machine.
The proxy installs itself on first use into a dedicated venv at
~/.local/share/claude-nvidia-proxy (the WSL2 system Python is
externally managed). No manual setup beyond the key:
printf '%s' 'nvapi-...' > ~/.nvidia-api-key && chmod 600 ~/.nvidia-api-keyNote. NVIDIA's own Claude Code documentation describes self-hosted NIM containers, which do expose
/v1/messagesnatively on port 8000 and need no proxy at all. That is a different product from the hosted catalog an API key buys, and its instructions do not apply here.
The pipeline is verified working. A plain round-trip returns correctly translated Anthropic JSON:
{"content": [{"type": "thinking", ...}, {"type": "text", "text": "ROUNDTRIP OK"}],
"stop_reason": "end_turn",
"usage": {"input_tokens": 13, "output_tokens": 25}}What makes it unusable is latency variance on NVIDIA's side. Measured on 2026-09-15, same account, same model, requests minutes apart:
| Request | Result |
|---|---|
/v1/models (catalog) |
HTTP 200 in 0.19 s, every time |
| Short completion, no tools | a few seconds — then, unchanged, no response in 55 s |
Completion with tools |
no response in 190 s |
| Short completion, retried later | HTTP 200, nvcf-status: fulfilled |
The catalog endpoint always answers instantly, so this is neither network nor auth: it is queueing on NVIDIA's NVCF backend, where hosted catalog models run on shared capacity. An interactive coding agent issues many sequential requests, and a multi-minute stall on any one of them makes the session unworkable.
Therefore this backend is kept but not used. For day-to-day work
prefer claude-local — no shared queue, predictable latency. The reason
to keep the NVIDIA path is access to models too large for 2×12 GB, for
one-off non-interactive questions where a long wait is acceptable.
Not verified: tool calling. Every tool-enabled request fell into the latency window, so whether tool calls survive the Anthropic→OpenAI translation is untested. Claude Code is unusable as an agent without it, so assume nothing here until it is measured.
Other findings worth keeping:
deepseek-ai/deepseek-coder-6.7b-instructis listed in the catalog but returns404 Not found for account— listed does not mean enabled. Check a model id withnvidia-modelsand one real request before setting it as a default.- The proxy's liveness probe is
/healthz, not the/healththat llama.cpp uses. It is held in$NVIDIA_PROXY_HEALTH_PATHso the convention lives in one place; probing the wrong path makes a perfectly healthy proxy look like a 60-second startup timeout.
claude-local is a single entry point over three local inference servers.
It auto-starts the requested backend, stops any other local backend
currently running, and routes claude at the right port.
| Backend | Port | Why |
|---|---|---|
| ollama | 11434 | Easy model management (ollama pull), stable default |
| llama.cpp | 8080 | Direct GGUF loading, latest architectures first, fine-grained GPU offload |
| ik_llama.cpp | 8081 | Fork of llama.cpp with aggressive CPU/hybrid kernels and new quant types (IQ4_KS, IQ2_KS); faster for MoE models that spill to system RAM |
Usage:
# default backend (from LOCAL_DEFAULT_BACKEND, default: ollama)
claude-local
# explicit backend
claude-local --backend llama
claude-local --backend ikllama
# override model
claude-local --model qwen2.5-coder:14b
claude-local --backend llama --model /path/to/model.gguf
# override context (llama/ikllama only; Ollama requires ollama-create)
claude-local --backend llama --ctx 128k
# GPU topology (Ollama only; HPC-safe single-GPU mode)
claude-local --gpus 1
# do not auto-start / auto-stop
claude-local --no-autostart # server must already be running
claude-local --no-autostop # keep server alive after exit
# pass-through args to the claude binary
claude-local -- --verbose some promptThe -- [CLAUDE_ARGS] form separates wrapper flags from the real claude
invocation — anything after -- goes straight to claude.
llm-local-server is the lower-level control verb. claude-local calls
it implicitly, but use it directly when you want the server up or down
independent of Claude.
llm-local-server start # start default backend
llm-local-server start --backend llama --ctx 128k # start llama-server with custom ctx
llm-local-server stop # stop default backend
llm-local-server stop --backend llama # stop specific backend
llm-local-server start --backend nvidia # start the NVIDIA translation proxy
llm-local-server restart [opts] # stop + start
llm-local-server status # process + loaded model + GPU usagestatus shows the PID, the bound port, and nvidia-smi GPU memory/utilization.
With no --backend it reports all four backends.
The backend must be given as --backend <name>, never positionally:
llm-local-server start ollama is rejected with an explicit error rather
than being silently forwarded to the server process.
ollama-pull [model] # pull a model (wraps `ollama pull`)
ollama-create [--ctx N] [model] # create a context-capped variant (context window in Ollama is per-model)
ollama-models # list models grouped by type (coder, chat, embedding…)
ollama-rename <old> <new> # rename a model (preserves exact casing)ollama-create --ctx N is the workaround for Ollama's static context.
It clones a model with a PARAMETER num_ctx N override so you can pin a
specific context length per session.
llama-models # list all GGUFs in $LLAMACPP_MODELS_DIR + HF cache
llama-alias <name> <path> # create short-name alias (symlink) in models dir
llama-alias <name> # remove alias
llama-info [model] # dump GGUF metadata (arch, params, quant) + tensor summaryAliases let you invoke heavy GGUF paths via a short name:
llama-alias qwen3-32b ~/models/Qwen3-32B-Instruct-Q4_K_M.gguf
claude-local --backend llama --model qwen3-32bllama-info reads the GGUF header and prints architecture (llama/qwen/
mixtral/…), total parameters, quantization, tensor count, and context
window — useful when auditing a model before loading.
~/.bash/claude_code.local is loaded at the end of the main claude_code
file. Use it for anything that differs per machine:
# Example ~/.bash/claude_code.local
export LOCAL_DEFAULT_BACKEND="llama"
export LLAMACPP_BIN="/opt/llama.cpp/build/bin/llama-server"
export IKLLAMA_BIN="/opt/ik_llama.cpp/build/bin/llama-server"
export LLAMACPP_MODELS_DIR="$HOME/models"
export LLAMACPP_DEFAULT_MODEL="qwen3-coder-30b"
export LLAMACPP_CTX=262144
export LLAMACPP_GPU_LAYERS=-1
export CUDA_VISIBLE_DEVICES="0,1"
export NVIDIA_DEFAULT_MODEL="deepseek-ai/deepseek-v4-flash-0731"Deploy it via its own machine-specific stow package (e.g.
bash-adam/ listed in machines/adam) — see
Machine-specific packages.
| Variable | Default | Purpose |
|---|---|---|
LOCAL_DEFAULT_BACKEND |
ollama |
Backend used when claude-local runs without --backend |
OLLAMA_HOST |
127.0.0.1:11434 |
Ollama server bind |
OLLAMA_KEEP_ALIVE |
10m |
Keep model in VRAM after last request |
OLLAMA_NUM_PARALLEL |
1 |
Concurrent request slots |
OLLAMA_MAX_LOADED_MODELS |
1 |
Prevent VRAM thrashing |
OLLAMA_DEFAULT_MODEL |
qwen2.5-coder:7b |
Default model for claude-local --backend ollama |
LLAMACPP_BIN |
llama-server |
llama.cpp server binary |
LLAMACPP_HOST / LLAMACPP_PORT |
127.0.0.1 / 8080 |
Bind address |
LLAMACPP_MODELS_DIR |
~/models |
Where llama-models / llama-alias operate |
LLAMACPP_DEFAULT_MODEL |
(unset) | Default GGUF path or alias |
LLAMACPP_GPU_LAYERS |
-1 |
GPU offload layers (-1 = all) |
LLAMACPP_CTX |
131072 |
Default context window |
IKLLAMA_BIN |
llama-server |
ik_llama.cpp server binary |
IKLLAMA_HOST / IKLLAMA_PORT |
127.0.0.1 / 8081 |
Bind address (distinct from mainline) |
IKLLAMA_GPU_LAYERS |
99 |
GPU layers (ik_llama.cpp treats -1 as 0, so a large sentinel is used) |
IKLLAMA_CTX |
131072 |
Default context window |
IKLLAMA_DEFAULT_MODEL / IKLLAMA_MODELS_DIR |
fall back to LLAMACPP_* |
Resolved lazily at call time |
NVIDIA_API_KEY / ~/.nvidia-api-key |
(required) | NVIDIA auth (nvapi-…, from build.nvidia.com) |
NVIDIA_DEFAULT_MODEL |
deepseek-ai/deepseek-v4-flash-0731 |
Default model for claude-nvidia |
NVIDIA_PROXY_HOST / NVIDIA_PROXY_PORT |
127.0.0.1 / 8787 |
Local translation-proxy bind |
NVIDIA_PROXY_VENV |
~/.local/share/claude-nvidia-proxy |
Venv holding nvd-claude-nim (auto-created) |
NVIDIA_PROXY_HEALTH_PATH |
/healthz |
Proxy liveness probe (not /health, which llama.cpp uses) |
OPENROUTER_API_KEY / ~/.openrouter-ai-key |
(required) | OpenRouter auth |
ZAI_API_KEY / ~/.z-ai-key |
(required) | Z.ai auth |
CLAUDE_LOCAL_GPUS |
(optional) | Shown in status line (<N>×GPU) when set by claude-local --gpus N |
Run claude-help at any time for the live version of this reference.
Everything lives flat in scripts/.scripts/ (deployed to ~/.scripts/), with
the sole exception of the Tecplot converters. The per-topic subdirectories this
table used to list disappeared in the migration to stow.
| Path | Purpose |
|---|---|
scripts/.scripts/bd |
Back-directory navigation — vendored, formerly a submodule |
scripts/.scripts/{convert,crop,scale,alpha,gray,overlap}_image |
Image processing utilities |
scripts/.scripts/{mountiso,mount_nas.sh} |
ISO and NAS mount helpers |
scripts/.scripts/{archive.sh,md-preview.sh,pps,rainix,rwd} |
Misc helpers (archive, preview, …) |
scripts/.scripts/borg-automated-backup.sh |
Borg backup automation |
scripts/.scripts/{pdf2grey,pdfA4scale,pdfcompress,image2pdf} |
PDF utilities |
scripts/.scripts/tecplot/ |
Tecplot format converters |
scripts/.scripts/{git-health,git-health-boot} |
Git integrity scanning — rationale and recovery playbook in scripts/.scripts/git-health.md |
scripts/.scripts/{noctalia-retheme,noctalia-qt-dim-disabled,noctalia-foot-fix-bright0} |
Noctalia palette switching and patches — quark |
scripts/.scripts/quark-desktop-install |
Root-owned desktop bits — quark |
scripts/.config/systemd/user/ |
systemd user units |
scripts/.bin/{act,hpc-login} |
Standalone binaries and HPC login helper |
scripts/.scripts/mbox-index.py |
Index Gmail Takeout MBOX into searchable SQLite (details) |
scripts/.scripts/skills-apply |
Declarative install/update/status for Claude Code skills across three classes (details) |
skills-apply is the per-host driver for the three-class skills machinery
introduced in Skills. It
reads:
~/.claude/settings.json→enabledPlugins(class B — plugin/marketplace)~/.claude/skills/manifest.toml→[skills.*]blocks (class C — third-party)~/dotfiles/machines/<hostname>.skills(optional per-host class-C filter)
…and delegates to the upstream installer for each class: claude plugin install/update/uninstall for B, the manifest's own install/update/
uninstall shell strings for C. Class A (custom user-authored) is managed
entirely by stow — skills-apply status simply reports whether each
expected symlink resolves.
skills-apply install # idempotent: skip already-installed entries
skills-apply update # update every installed skill
skills-apply status # group by class, mark drift, exit 0
skills-apply remove <name> # uninstall (plugin or manifest); does NOT
# rewrite settings.json or manifest.toml —
# script warns you to do that yourselfRestart Claude Code after install or update so plugin changes load
(the claude plugin update CLI itself says "restart required to apply").
Dependencies: claude (Claude Code CLI), python3 (for inline tomllib
JSON/TOML parsing of settings.json and manifest.toml).
Reclaim Google account quota by exporting Gmail to a local archive that stays searchable after you delete the cloud copy. Indexes a Google Takeout MBOX export into a SQLite FTS5 database and extracts attachments to a date/sender file tree. Stdlib-only — no dependencies.
# 0. Get the mbox: Takeout (Mail → MBOX) → download zip → unzip
unzip -o takeout-*.zip -d /mnt/d/gmail-backup # yields Takeout/Mail/*.mbox
# 1. Build the searchable index + extract attachments
python3 ~/.scripts/mbox-index.py build /mnt/d/gmail-backup \
--db /mnt/d/gmail-backup/mail.db --attach-dir /mnt/d/gmail-backup/attachments
# 2. Search (full-text + filters: from: to: subject: has:attachment larger:N before:/after:)
python3 ~/.scripts/mbox-index.py search "invoice AND from:acme larger:5M after:2020-01-01"
python3 ~/.scripts/mbox-index.py search "larger:25M" # find quota hogs before deleting
# 3. Verify the archive, then delete from Gmail by size and empty Trash to reclaim quotaFull workflow — including the Takeout export, archive verification, and the cloud-deletion
steps — is in scripts/.scripts/mbox-index.md.
Machine-specific (machines/quark); adam never stows it. quark runs CachyOS with
sway and the Noctalia desktop shell, which replaced waybar and supplies the bar,
launcher, notifications, OSDs and lock screen.
desktop/
├── .config/
│ ├── sway/config compositor: keybinds, input, borders
│ ├── foot/foot.ini terminal — includes the generated theme
│ ├── qt6ct/qt6ct.conf Qt6: Fusion style + custom palette
│ ├── chrome-flags.conf Chrome: native Wayland + GTK4
│ └── noctalia/patches/README.md why the local patches exist
├── system/ root-owned, not stowed; paths mirror /
│ ├── udev/hwdb.d/61-evdev-local.hwdb touchpad fuzz
│ ├── modprobe.d/iwlwifi-no-powersave.conf
│ └── NetworkManager/conf.d/wifi-powersave-off.conf
└── .stow-local-ignore keeps system/ out of the stow tree
Disabled here for the same reason as on astrobit — client-side power save hurts
Moonlight as much as the server side hurts Sunshine — but with different knobs.
quark's card is Intel (iwlwifi), where power_save is already off by default;
the culprit is iwlmvm.power_scheme, which defaults to 2 (balanced) and must be
1 (active). Do not add d0i3_disable: it does not exist in this kernel.
Dropping a file into NetworkManager/conf.d is not sufficient on its own. NM
consults that default only for profiles expressing no preference, and
nmcli device reapply reapplies IP settings rather than radio ones — so the
connection profile itself has to carry powersave=2, which is what
quark-desktop-install sets.
Noctalia renders the active palette into per-application config files ("templates"), which is what keeps foot, alacritty, GTK3/4, Qt6, btop and sway borders on one palette. The current scheme is Solarized dark.
Those generated files are gitignored — they are rebuilt on every palette change, and
a stow directory symlink would make Noctalia write into the repo. desktop/ therefore
tracks individual files, never a directory whose siblings Noctalia generates.
Enabling the template set is a GUI-only step (settings → Templates: gtk3, gtk4, qt, foot, alacritty, btop, sway); there is no supported settings.toml representation.
noctalia-retheme --list # schemes + current
noctalia-retheme builtin Nord # switch
noctalia-retheme community Solarized # switch backBuiltins: Catppuccin, Dracula, Gruvbox, Kanagawa, Nord, Oxocarbon. Afterwards: open a new
terminal, restart GTK/Qt apps, swaymsg reload.
The wrapper exists because color-scheme-set alone leaves the daemon rendering the old
palette (it needs an explicit config-reload), and templates-apply returns before the
files are written. It also re-applies two patches Noctalia would otherwise clobber:
noctalia-qt-dim-disabled (Qt disabled widgets are emitted identical to enabled ones) and
noctalia-foot-fix-bright0 (the lifted ANSI 8 repaints vim's editor background). Rationale
and measurements: desktop/.config/noctalia/patches/README.md.
bash ~/dotfiles/dotify.sh desktop # symlinks the configs
~/.scripts/quark-desktop-install # touchpad hwdb + WiFi power save (root)Then enable the Noctalia templates in the GUI and run noctalia-retheme.
astrobit runs CachyOS with niri, a scrollable
tiling Wayland compositor, installed from the cachyos-niri-noctalia preset. It is
a separate package from desktop/ because almost nothing is shared with quark:
different compositor, and a foot config that does not read a generated theme.
| File | Deployed to | Purpose |
|---|---|---|
.config/niri/config.kdl |
~/.config/niri/config.kdl |
Nothing but include lines |
.config/niri/cfg/*.kdl |
~/.config/niri/cfg/ |
animation, autostart, display, input, keybinds, layout, misc, rules |
.config/foot/foot.ini |
~/.config/foot/foot.ini |
Terminal — Solarized inlined, not templated |
niri's own config is a single config.kdl; the split into cfg/*.kdl with
include lines comes from the preset and is kept because it makes a single
concern (keybinds, window rules) editable without scrolling through the rest.
Both levels are tracked, and stow links them file by file — the directories
in ~/.config/ stay real. That is deliberate: if the Noctalia templates are
ever enabled here, the daemon will write ~/.config/niri/noctalia next to the
symlinks instead of straight into this repository, which is the same rule
desktop/ follows for quark.
Editing backups (*.bak) left in ~/.config/niri/cfg/ are untracked on purpose.
astrobit is normally headless, reached through a web service; ssh covers most failures. Sunshine is the third fallback, for when the fault is in the GUI — a modal dialog blocking Ekos, a mount connection wizard. It therefore streams the live niri session: a dedicated RDP session would open a clean desktop next to the broken one, useless precisely when it is needed.
| File | Tracked | Why |
|---|---|---|
.config/sunshine/sunshine.conf |
yes | Hand-written; Sunshine does not rewrite it |
.config/sunshine/apps.json |
yes | The web UI does rewrite this — edits there show up as a diff |
sunshine_state.json, credentials/, *.log |
no | Web-UI password, TLS keypair, pairing state — per machine, never in git |
capture and encoder are deliberately not set: forcing capture = wlgrab
makes startup fail with "Could not initialize display with the given hw device
type". Autodetect picks wlgrab (niri does expose zwlr_screencopy_manager_v1,
despite being smithay rather than wlroots) and VAAPI on its own.
Resolution, frame rate and bitrate are client-side settings: fps,
resolutions and min_bitrate are not Sunshine options and are silently
ignored. Set them in Moonlight on quark — 1920x1080, 30 fps, ~15 Mbps.
desktop-astrobit/system/ is outside the stow tree (excluded by
.stow-local-ignore). Run sudo ~/.scripts/astrobit-system-install after
stowing to install the WiFi power-save settings and the virtual EDID. Idempotent.
Two things it configures deserve their reasons recorded:
- WiFi power save, on two levels. The RTL8821CE's
rtw88driver enables Deep Power Save by default, parking parts of the radio between beacons. On a link measured at -45 dBm with zero tx retries, that alone produced the Moonlight stutter and disconnections inherited from the Windows install. NetworkManager (wifi.powersave = 2) covers mac80211; Deep PS is reachable only as a module parameter, so it needs a reboot. quark needs the same fix with different knobs — Inteliwlwifi, wherepower_saveis already off butiwlmvm.power_schemedefaults to 2 (balanced) and must be 1 (active). - Virtual EDID. niri has no virtual outputs and no headless mode: unplug the monitor and it loses its only output. The blob advertises a permanent 1920x1080 on HDMI-A-2 — lower than the real 3440x1440 ultrawide, because this is what gets streamed. It replaces the real EDID always, so the physical monitor also runs at 1080p.
- Colours are inlined (Solarized dark,
[colors-dark]) rather than pulled in withinclude=~/.config/foot/themes/noctalia: no Noctalia template is enabled on this host, so there is no generated theme file to include. term=xterm-256colorinstead of the defaultterm=foot, because thefootterminfo entry does not exist on the remote HPC hosts and an unknownTERMbreaks curses applications over ssh.- Plain
monospaceat size 12: no Nerd Font is installed here, and the prompt degrades gracefully without the powerline glyphs.
If the config belongs to a tool already covered by a stow package, place the file at the correct mirrored path inside that package and re-run stow:
# Example: add a new bash helper
cp my-helper ~/dotfiles/bash/.bash/my-helper
# Re-deploy the package (safe to re-run, stow is idempotent)
bash ~/dotfiles/dotify.sh bashStow will create ~/.bash/my-helper → ~/dotfiles/bash/.bash/my-helper.
-
Create the package directory and mirror the
$HOMElayout inside it:# Example: track ~/.config/foo/config mkdir -p ~/dotfiles/foo/.config/foo cp ~/.config/foo/config ~/dotfiles/foo/.config/foo/config
-
Add the package name to
PACKAGESindotify.sh:PACKAGES=(bash vim git claude modules python scripts miscellanea foo)
-
Deploy:
bash ~/dotfiles/dotify.sh fooStow will symlink
~/.config/foo/config → ~/dotfiles/foo/.config/foo/config. -
Commit:
git add dotify.sh foo/ git commit
For configs that only belong on one machine, list the package in
machines/<hostname> (one package name per line):
echo "foo" >> ~/dotfiles/machines/$(hostname -s)dotify.sh reads this file automatically and stows those packages after the
common ones. The machines/ directory is tracked in git so the per-host
setup is reproducible.
My dotfiles come from many years of GNU/Linux usage and inspiration from countless people sharing their configs on the web. Distributed under the terms of the WTFPL — Do What the Fuck You Want to Public License, without any warranty.
Go to Top