Personal configuration files for my macOS development environment, centered on Neovim, Zsh, and Ghostty.
- macOS
- No other prerequisites —
setup.shinstalls Homebrew and everything else fromBrewfile.
git clone https://github.com/ronload/dotfiles.git ~/dotfiles
cd ~/dotfiles
./setup.shsetup.sh installs Homebrew, Rust, and every package in Brewfile, then hands off to install.sh to symlink configs, clone fzf-git.sh, and sync Neovim plugins. Each step is idempotent and safe to re-run.
- nvim: Neovim with lazy.nvim and TokyoNight Moon
- zsh: Zsh shell (zshrc, zprofile, zshenv, aliases, prompt, abbreviations)
- ghostty: Ghostty terminal
- git: Git global config (with conditional work identity include)
- gh: GitHub CLI
- fastfetch: System info with a custom colored-bar module
- karabiner: Karabiner-Elements keyboard customization
- zmk: Tofu60 keymap changes and the Bluetooth client that applies them
- yazi: Terminal file manager
- herdr: Agent-aware terminal multiplexer
- Brewfile: Homebrew package manifest
Linting and formatting are driven by just. The justfile is the single source of truth — GitHub Actions invoke the same recipes on every push and pull request.
just # list all recipes
just ci # run every linter and formatter check
just lint # run all linters (lua, shell, zsh)
just format # run all formatter checks (lua, shell)
just lint-lua # luacheck on tracked Lua files
just lint-shell # shellcheck on tracked shell scripts
just lint-zsh # zsh -n syntax check on zsh/*
just format-lua # stylua --check
just format-shell # shfmt -i 2 -ci -d
just test # run all tests
just test-install # verify install.sh reproduces every declared symlinkAll linter recipes operate on git ls-files, so untracked files are skipped. Zsh files are validated by zsh -n rather than shellcheck, which does not support zsh.
install.sh linking is verified on every PR (just test-install, via install-test.yml): runs install.sh against a disposable copy of the repo and a sandbox $HOME, then asserts every declared symlink exists, points at the right target, is not broken, and that a second run re-links nothing. Fully offline and isolated; touches neither the real working tree nor the real home directory.
Two details make reproduction location-independent and Karabiner-safe:
- Several configs reference the repo by the absolute path
~/dotfiles(tmux, git, ghostty, fastfetch, zsh), and those tools cannot resolve the path dynamically.install.shtherefore ensures~/dotfilespoints at the repo, so it can be cloned anywhere; cloning to~/dotfilesdirectly needs no extra link. An existing real~/dotfilesthat is not this repo is left untouched and the install aborts. ~/.config/karabineris symlinked as a whole directory rather than justkarabiner.json, because Karabiner-Elements replaces a symlinked config file on save (per its docs). A pre-existing real config directory is backed up to.bakbefore linking.
Key remapping is split by scope, so each change lives in one place:
- Karabiner (
karabiner/karabiner.json) holds what applies to every keyboard: Caps Lock is Control when held and switches the input source when tapped, Ctrl+B/F/N/P move the cursor Emacs-style, and the tmux prefix Ctrl+S in Ghostty switches to ABC first. - Keyboard firmware holds layout fixes for one keyboard. The Tofu60 (HHKB layout, ZMK firmware) gets
`at the top right, Backspace below it, and Command next to the space bar;zmk/tofu60.jsonlists these changes against its stock keymap.
just zmk-check # compare the keyboard with zmk/tofu60.json
just zmk-apply # write missing bindings and save them to the keyboard
just zmk-dump # print the whole keymapThe recipes build zmk/studio-cli, a small ZMK Studio client, and talk to the keyboard over Bluetooth, so neither a cable nor the ZMK Studio app is needed. macOS asks for Bluetooth access on first use and again after the client is rebuilt. The bindings are stored on the keyboard, so just zmk-apply is only needed for a new or reset keyboard.
The Tofu60 also identifies as a pointing device, because its firmware has ZMK mouse keys, and Karabiner ignores pointing devices by default. karabiner.json therefore lists its Bluetooth and USB IDs with "ignore": false.
git/gitconfig conditionally includes a work identity for repos under git@github.com:prinsur/**:
[includeIf "hasconfig:remote.*.url:git@github.com:prinsur/**"]
path = ~/.gitconfig-prinsurThis override file is not tracked. Create it manually on each machine:
cat > ~/.gitconfig-prinsur <<'EOF'
[user]
email = your-work-email@example.com
EOFWithout it, commits to prinsur/* repos fall back to the global identity.
Released under the MIT License. See LICENSE for details.
