Contributing
Adding a config
Section titled “Adding a config”- Add the file to the repo. Configs that live in
~/.config/<tool>/go in.config/<tool>/in the repo; classic dotfiles go at the root (.name). - Add an
install_<tool>function ininstall.sh, modelled oninstall_kittyorinstall_nvim: get the file withfetch_source, warn if the tool is missing, back up, copy. - Call it inside
main, afterconfirm_plan. - If it contains paths that differ per OS, add a global in
detect_systemand rewrite withsedwhile copying (see Installer internals). - Add a page for it in
docs/src/content/docs/and to the sidebar indocs/astro.config.mjs, and update What gets installed.
Conventions
Section titled “Conventions”- Commit messages: Conventional Commits —
feat(tmux): add plugin,fix(install): …,docs: …. - Use
$HOME, not~, in scripts.~isn’t expanded inside quotes or in many other places. - Comment the why. New code says briefly why it exists and what would break without it. Don’t remove existing explanatory comments.
- Both shells. Anything in
.aliases/.functionsmust work in bash and zsh — guard shell-specific code with$ZSH_VERSION/$BASH_VERSION. - Both systems. Check GNU (Linux) vs. BSD (macOS) differences; pick with
$IS_MACOS.
Testing the installer
Section titled “Testing the installer”Docker gives a clean system in seconds. From the repo root:
# Non-interactive run, twice — the second run should install nothingdocker run --rm -v "$PWD":/d:ro ubuntu:26.04 bash -c \ 'cp -r /d /tmp/d && cd /tmp/d && ./install.sh -y && ./install.sh -y'
# The real curl | bash path, with answers typed in through a fake terminalprintf '2\n3\nja_JP\n\n' | docker run --rm -i -v "$PWD":/d:ro debian:stable-slim bash -c \ 'cp -r /d /tmp/d && cd /tmp/d && script -qec "cat install.sh | bash" /dev/null'-v "$PWD":/d:romounts the repo read-only; the script copies it so the original can’t change.script -qec "…" /dev/nullruns the command inside a pretend terminal, so the installer thinks a person is typing; theprintftext becomes the keypresses.
Images used for testing: ubuntu:26.04, debian:stable-slim, fedora:latest,
archlinux:latest, opensuse/tumbleweed, alpine:3.20.
Working on the docs
Section titled “Working on the docs”The site is built with Starlight (on Astro) and lives in
docs/. It needs Node.js 22.12 or newer.
cd docsnpm install # oncenpm run dev # live preview at http://localhost:4321/dotfiles/npm run build # production build into docs/dist/, including the search index- Pages are Markdown in
docs/src/content/docs/. Use.mdxwhen you need components like tabs or cards. - Links between pages must start with
/dotfiles/— the site is served from that sub-path on GitHub Pages. - Images go in
docs/src/assets/and are linked relatively. - Theme modes:
src/components/ThemeProvider.astroandThemeSelect.astroreplace Starlight’s versions to add high contrast; the colours are insrc/styles/theme.css. - Text size: the A− / A+ buttons are
src/components/TextSize.astro, shown insideThemeSelect.astro. The size is stored and applied inThemeProvider.astro, andsrc/styles/theme.cssscales Starlight’s--sl-text-*sizes by it. Only text grows, not the column widths, so the layout keeps working at 150%. - Search (Pagefind) only works in a build (
npm run build && npm run preview), not innpm run dev.
Screenshots
Section titled “Screenshots”The terminal screenshots (peaclock, Vim, Neovim, git lg) are made by
docs/scripts/shot.py. It runs a program in a virtual terminal and draws the screen with the
Lasik colours and Hasklug Nerd Font, so every screenshot looks the same and none depend on your
own terminal setup.
Needs: uv, headless Google Chrome and the Hasklug Nerd Font
(the installer adds it on desktops). uv run --with pyte --with pillow fetches the two Python
libraries for that one run, without installing them.
Keep personal details out. Programs run with a throwaway home folder (HOME=$(mktemp -d))
holding only the repo’s configs, so no history, hostname or username shows. Look at every
image before committing it: once pushed, it’s public.
Run from the repo root. Each command overwrites the image on the docs site:
S="uv run --with pyte --with pillow python3 docs/scripts/shot.py"IMG=docs/src/assets/screenshots
# peaclock, with the repo's config in a temporary folder (peaclock writes files next to it)P=$(mktemp -d) && cp .config/peaclock/config "$P/"$S $IMG/peaclock.png 70 18 3 -- peaclock --config-dir "$P"
# Vim and Neovim: a throwaway home with only the repo's configs, opened at run_step() in install.shH=$(mktemp -d) && mkdir -p "$H/.vim/colors" "$H/.config/nvim"cp .vimrc "$H/" && cp .vim/colors/sublimemonokai.vim "$H/.vim/colors/" && cp .config/nvim/init.lua "$H/.config/nvim/"L=$(( $(grep -n '^run_step()' install.sh | cut -d: -f1) - 12 ))HOME=$H $S $IMG/vim-monokai.png 100 28 3.5 --keys "2.5:\x0c" -- vim -n "+set t_ut=" "+$L" "+normal zt" install.shHOME=$H XDG_CONFIG_HOME=$H/.config XDG_DATA_HOME=$H/.local/share XDG_STATE_HOME=$H/.local/state \ $S $IMG/neovim.png 100 28 3.5 --keys "2.5:\x0c" -- nvim -n "+$L" "+normal zt" install.sh
# git lg, using only the repo's .gitconfigGIT_CONFIG_GLOBAL=$PWD/.gitconfig $S $IMG/git-lg.png 150 17 2 -- git -c color.ui=always --no-pager lg -n 17--keys "2.5:\x0c"presses Ctrl+L after 2.5 seconds, a redraw, so the picture is clean."+set t_ut="makes Vim paint every cell itself. Without it, the virtual terminal leaves gaps in Monokai’s grey background.HOME=$(mktemp -d)uses a new empty folder as the home folder for that one command.vim -n/nvim -n: no swap file. The editor is stopped, not closed, after the picture, so it would leave a.install.sh.swpbehind.
docs/scripts/tmux_preview.py draws two tmux themes side by side (each in its own throwaway
tmux server), for comparing theme changes. Its status bar shows the machine’s hostname, so it’s
for previews, not for the docs.