Skip to content

edheltzel/dotfiles

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3,727 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
β–ˆβ–ˆβ•”β•β•β•β•β•   β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—     β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
β–ˆβ–ˆβ•”β•β•β•     β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β•šβ•β•β•β•β–ˆβ–ˆβ•‘
β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘
β•šβ•β•β•β•β•β•β•β•šβ•β•β•šβ•β•β•β•β•β•  β•šβ•β•β•β•β•β•    β•šβ•β•   β•šβ•β•β•β•β•β•β•

E.Dots

Note

EdHeltzel's Dotfiles

My personal setup for 🐠 Fish shell on  MacOS - v3

Hey there πŸ‘‹, I'm EdHeltzel and you've found my dotfiles setup for working with fish shell on ο£Ώ macOS managed with GNU Stow. You'll also find files for provisioning a new machine and setting up my environment, Including Neovim (via NEO.ED), WezTerm - my AI Development Environment (ADE) of choice for Agent Harness/Orchestration/Workflow (whatever buzz term you want to use). VSCode and Zed configurations are maintained for legacy/compatibility reasons but are not part of the regular workflow.

Warning

Again, this is my personal setup and changes often, so don't blindly fork and run the install.sh script without reading it first. The script uses subcommands β€” see ./install.sh help for usage.

But get inspired, take what you want, and leave the rest to make it your own.

Screenshots Screenshots
1-aerospace+sketchbar 2-bat
3-btop 4-fastfetch-fish
5-lazygit 6-nvim-neoed-picker
7-nvim-neoed-dash 8-theme-switcher
9-vscode 10-yazi
Different Versions
  • v1 uses oh-my-zsh (very old)
  • v2 uses fish shell + custom scripts (old)
  • v3 uses fish shell + GNU Stow (recent)
  • v3.2 includes Zsh config (current)

Table of Contents:

Prerequisites ↑

Install with a single line...

I have not tested this on a fresh install, so this could break your setup. I'd suggest you read through the install.sh script and the justfile before running this command.

In theory, this will clone the repository to ~/.dotfiles, then bootstrap the machine. Again, in theory.

bash -c "$(curl -fsSL https://raw.githubusercontent.com/edheltzel/dotfiles/master/install.sh)" -- bootstrap

The remote-curl invocation detects it's running outside a cloned repo, clones itself to ~/.dotfiles, then re-executes with the bootstrap subcommand. If you prefer, clone first and run locally:

git clone --recurse-submodules https://github.com/edheltzel/dotfiles.git ~/.dotfiles
cd ~/.dotfiles && ./install.sh bootstrap
Resources & Inspiration for the help

Below are the resources I used to get to this point in my setup.

My Equipment - Keyboards & Trackballs

I collect, build, and use different ergonomic keyboards and trackballs. Generally I'm running some kind of erognomic split keyboard with the trackball in between.

Trackballs:

Caveats for non-Apple Silicon (Intel) If you are on any version of macOS that uses AFPS, you'll need to disable the SIP. First check to see if SIP is enabled or not.
csrutil status

output should read:

System Integrity Protection status: enabled.

If your SIP is enabled, then follow the next steps to disable it – Assuming that you know what you're doing, here is how to turn off System Integrity Protection on your Mac.

  1. Turn off your Mac (Apple > Shut Down).
  2. Hold down Command-R and press the Power button. Keep holding Command-R until the Apple logo appears.
  3. Choose Utilities > Terminal.
  4. Wait for OS X to boot into the OS X Utilities window.
  5. Enter csrutil disable.
  6. Enter reboot.
  7. csrutil status -> should read System Integrity Protection status: disabled.

πŸ‘‹ For future Mr EdHeltzel↑

Since we have a bad habit of forgetting things - see Troubleshooting:

  1. Installing Xcode Command Line Tools

    • sudo softwardupdate -i -a && xcode-select --install This will install git and make if not already installed.
  2. Generate a new SSH key and add to GitHub

  3. Clone repo with submodules

    • git clone --recurse-submodules https://github.com/edheltzel/dotfiles.git ~/.dotfiles
    • Or if already cloned: cd ~/.dotfiles && git submodule update --init --recursive
  4. Use the justfile for the rest of the setup

    • cd ~/.dotfiles && just install (calls ./install.sh bootstrap)
    • Or invoke the script directly: cd ~/.dotfiles && ./install.sh bootstrap
    • For stow-only (no software install): ./install.sh link or just link
    • Note: The install script automatically initializes git submodules
  5. After the setup is complete, run upp to execute topgrade and update everything.

    • upp is an alias for topgrade which is Update Packages (this is what I say to myself).
    • The topgrade.toml includes [post_commands] for additional Brew and Node updates.
  6. Optional steps for DX and nice to haves:

    • Disable Gatekeeper when installing apps: sudo spctl --master-disable (in macos/security.sh)
    • Make sure to run fnm env --use-on-cd | source to enable auto-switching of Node versions. (in fish)
  7. Wallpapers are stored in ~/.wallpapers/ which now lives in it's own repo here

    • Raycast uses this repo/directory to set wallpapers

The Nitty Gritty ↑

Originally, I used a series of custom scripts to create symlinks, and it worked, but I've since switched to using GNU Stow. This is way easier to manage.

So, with the addition of GNU Stow, I added a justfile – I treat this like NPM scripts. You need to be in the root of ~/.dotfiles to execute any of the just recipes.

The following are available:

default       Show available recipes (default)
install       Bootstrap a new machine [alias: bootstrap]
link          Symlink all dotfiles w/Stow [alias: run]
list          List available stow packages
stow          Add individual packages w/Stow [alias: add]
unstow        Remove individual packages w/Stow [alias: remove]
update        Restow all packages w/Stow [alias: up]
delete        Remove all dotfile symlinks

Bootstrapping

  • just install calls ./install.sh bootstrap under the hood. The unified installer has two primary subcommands:
    • ./install.sh bootstrap β€” provision a new machine (Xcode CLT, Homebrew, language package managers, Stow, duti, macOS prefs, git config, optional fish as default shell).
    • ./install.sh link β€” symlink dotfiles only (idempotent). Use this when the machine is already set up.
  • Supported flags for bootstrap: -y/--yes (skip prompts), --no-packages (skip Homebrew bundle), --no-macos (skip system preferences).
  • Run ./install.sh help for full usage.

Stowing/Unstowing (add/remove)

There are two options for managing packages with GNU Stow:

  1. Just use Stow directly: stow nvim / stow --restow nvim or stow -D nvim (unstow)
  2. Use the justfile: just stow nvim or just unstow nvim

Stow Packages

  • dots (dots/)
    • Misc dotfiles stored in $HOME (.npmrc, .tmux.conf, .biome.json, etc.)
  • git (git/)
    • Git configuration with SSH signing, Delta pager, machine-specific local configs
  • fish (fish/)
    • XDG Base Directory – Reference: XDG Base Directory for more information. To edit/set the XDG Base Directory variables, you can edit the ~/fish/.config/fish/conf.d/paths.fish file. Hopefully, this will keep the $HOME directory clean and organized.
    • Secrets Management: API keys and sensitive environment variables are stored in conf.d/secrets.fish (gitignored). This file is created locally and never committed to version control.
      • Copy the template: cp fish/.config/fish/conf.d/secrets.fish.example fish/.config/fish/conf.d/secrets.fish
      • Or create manually and add your keys:
        # secrets.fish - API keys and sensitive environment variables
        set -gx ANTHROPIC_API_KEY "sk-ant-..."
        set -gx OPENAI_API_KEY "sk-..."
      • Used by: claude-models function (lists available Anthropic models)
    • Lazy-Loading Architecture: Fish config is optimized for fast shell startup using lazy-loading patterns. Heavy tools are only initialized on first use:
      • conf.d/fnm.fish - Node version manager (fnm) initialized on first node/npm call
      • conf.d/zoxide.fish - Directory jumper (z/zi commands) initialized on first use
      • Pattern: persistent wrapper functions call the real tool init once (guarded by functions -q), then delegate to the underlying command. Wrappers are NOT self-erasing β€” this lets them add post-jump behavior (e.g. z calls __list_dir after jumping). For zoxide, --no-cmd is used so zoxide's own alias z=__zoxide_z doesn't overwrite our wrapper.
    • Shared directory listing (__list_dir): functions/__list_dir.fish centralizes the eza flags used after every directory change. Both cd and z/zi call it, so every navigation produces the same listing β€” icons, git status, grouped dirs-first, no noise columns.
    • See config.fish for the main lazy-loading orchestration
  • zsh (zsh/)
    • Near-identical mirror of the Fish config (~90-95% feature parity) for Zsh compatibility. XDG-compliant (ZDOTDIR=~/.config/zsh), keeping $HOME clean.
    • Plugin Manager: Antidote β€” plugins defined in .zsh_plugins.txt:
      • zsh-autosuggestions - Fish-style inline history suggestions
      • fast-syntax-highlighting - Real-time command coloring
      • zsh-abbr - Fish-style abbreviation expansion
      • zsh-history-substring-search - Fish-style up/down history search
      • zsh-autopair - Auto-close brackets/quotes
      • zsh-completions - Additional completion definitions
    • Modular Architecture: .zshrc.d/ numbered configs loaded in order (01-paths through 12-lazy-zoxide), matching Fish's conf.d/ pattern
    • Lazy-Loading: FNM, rbenv, and Zoxide are lazy-loaded on first use (same pattern as Fish)
    • Secrets Management: secrets.zsh (gitignored) β€” copy from secrets.zsh.example
    • Prompt: Configurable via ZSH_PROMPT variable β€” Starship (default) or Oh My Posh
    • Functions: 30+ autoloaded functions in functions/ β€” same set as Fish (theme, reload, flashEthernet, etc.)
    • Completions: Custom completions in completions/ (voicemode, fab, obsidian-cli, alacritty)
  • config (config/)
    • Configuration files for 25+ applications, instead of adding them to root of the repo.
    • Prompt: Configurable prompt system supporting Oh My Posh (default) or Starship
      • starship-ish.omp.json - Oh My Posh theme (styled like Starship)
      • starship.toml - Starship config (alternative)
      • Switch prompts by changing FISH_PROMPT in fish/.config/fish/config.fish
    • Theme Switcher: Unified theme switching across multiple applications
      • Run theme for interactive fzf picker with preview, or theme <name> to switch directly
      • Supported themes: eldritch, rose-pine, rose-pine-moon, tokyo-night, tokyo-night-moon
      • Apps updated: Ghostty, Kitty, WezTerm, Neovim, bat, btop, lazygit, eza, oh-my-posh, OpenCode
    • Terminal configs: alacritty, kitty, ghostty, wezterm
    • Keyboard: karabiner (legacy, now using LeaderKey), leaderkey config
    • Editors: zed (Vim mode + AI integration) β€” maintained for occasional use
  • neoed (neoed/) - Git Submodule (repo)
    • NEO.ED - LazyVim-based Neovim configuration β€” the primary editor
    • See neoed/.config/nvim/README.md for full documentation
    • Key Features:
      • AI Integration: Claude Code, OpenCode, Pi Agent
      • Multi-language support: Go, Python, TypeScript, PHP/Laravel, Rust, and more
      • Eldritch colorscheme with custom lualine statusline
      • Snacks.nvim for explorer, picker, dashboard, and terminal
      • Biome-first formatting with Prettier fallback
    • Plugin Organization: lua/plugins/{ai,coding,editor,formatting,languages,linting,ui,utils}/
    • Stow creates a symlink: ~/.config/nvim β†’ ~/.dotfiles/neoed/.config/nvim/
  • local (local/)
    • User-specific data: ~/.local/bin scripts, cspell dictionaries, keyboard config backups (Corne, Dygma Defy, Keychron Q11), GitHub CLI extensions

Scripts ↑

Any of the scripts can be run individually at any time to update/reset as needed. ie: cd ~/.dotfiles && ./duti/duti.sh

  • macOS (macos/)
    • macos.sh - Executes a long list of commands pertaining to macOS Preferences – DO NOT blindly run this script - it is a WIP with each macOS update things change.
  • packages (packages/)
    • packages.sh - Installs the Brewfile and each package manager's packages based on the .txt files.
  • repositories (repos/)
    • repos.sh - Clones the repositories in the .txt files at the corresponding locations
  • private (private/)
    • private.sh - Left empty on purpose
  • duti (duti/)
    • duti.sh - Sets the default applications for file types
      • run ./duti/duti.sh to reset the default applications for file types
  • Helper Scripts (scripts/)
    • functions.sh - Contains helper functions for for the scripts

MacOS Mods ↑

Note

For Karabiner Elements, I'm constantly changing my config to better fit my workflow and preferences. I've move away from using the Keychron Q11 to using a Dygma Defy

~~Aerospace Window~~ Native MacOS Stage Manager + Raycast + [Alt-Tab](https://alt-tab.app/) ↑

Ice Bar ↑

I only use [Ice.app](https://icemenubar.app/) to change the appearance of the native MacOS menu bar.

Karabiner Elements ↑

For most of my keyboard hacking, I'm using a combination of QMK (thru VIA app), with Raycast, but I leverage Karabiner Elements for more complex modifications, like chording the Hyper Key with other modifiers.

My Hyper Key: right_cmd + right_shift + right_option + right_control (notice that it is the right side modifiers only.) - This is still relevant with the Dygma Defy, I just use the Bazecore software to setup a Hyper key with the LeaderKey app for chording keys and Raycast for non-chorded keys: ie: non-chorded: hyper + t launches Wezterm, my current default terminal - handled by Raycast chorded: hyper + r + d launches my Dotfiles in my default editor - handled by LeaderKey

See the Readme for more details: config/.config/karabiner/README.md

[!INFO] I have migrated to using LeaderKey and relying on my Dygma Defy with Bazcore

Troubleshooting ↑

Dotfiles↑

Fish: Fisher Plugin Manager In the past, Fisher (fish plugin manager) would do something weird or introduce a breaking change - just reinstall Fisher.
curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source && fisher install jorgebucaran/fisher
Node Development: FNM

Node Version switching for Node development, takes advantage of fnm for managing Node versions, which supports both .nvmrc and .node-version files.

  • If not already installed from the Brewfile, install fnm:
brew install fnm
fnm env --use-on-cd | source

For Fish Completions run:

fnm completions --shell fish

Make sure you run:

just update #updates all stow packages
OR
just stow fish

Enable auto switch of Node versions with .node-version or .nvmrc files

# auto runs: fnm use
fnm env --use-on-cd | source

Which each Node version change, enabling corepack is necessary to ensure that pnpm and yarn are available.

npm install --global $(cat node_packages.txt)
Git: Commit and Tag Signing

SSH Signing

I use SSH commit signing over GPG. GPG is there if I need it, but I prefer SSH. For a few resources to help get this setup:

The .gitconfig includes .gitconfig.local

  [meta]
    isLocalConfig = true
  [user]
    signingkey = PATH_TO_YOUR_KEY
  [gpg "ssh"]
    allowedSignersFile = PATH_TO_YOUR_ALLOWED_SIGNERS_FILE

If you choose to use this, make sure you look at that ./git/git.sh; this script is where the provisioning of .gitconfig.local happens.

GPG Commit Signing - optional

GPG signing is set to TRUE by default. If you rather not enable GPG then execute: git config --global commit.gpgsign false and remove the GPG packages from the Brewfile.

renew expired gpg

Generate new key and assign to global git config

Main take away:

  • gpg --list-secret-keys --keyid-format=long
  • Copy key
  • set key for your git user
    • git config --global user.signingkey <your key>
  • If you need help setting this up GPG:
  • Please Note if you used the Brewfile, Cask installed the macOS GPG Suite via cask 'gpg-suite-no-mail' -- (alternatively) update the Brewfile with `cask 'gpg-suite' to include GPGMail.
Rust and Cargo From time to time, `cargo` will fail to update/upgrade using `topgrade`. This is generally due to something changing inside of the Rust system that doesn't allow `cargo install cargo-update` to work.

The solution: Uninstall and reinstall rust and rustup-init along with cargo using brew.

brew uninstall rustup-init;
and brew reinstall rust;
and cargo uninstall cargo;
cargo install cargo-update --force;
topgrade --only cargo
SSH Agent In the even when restarting MacOS, the SSH agent will not be running, even though it is configured to run on login. A result of this is that Git will keep asking for your SSH Passphrase, to resolve this you will need to execute the following:
eval ssh-agent -s;
and ssh-add --apple-use-keychain

What this does: Starts the SSH agent and adds the SSH key to the keychain.

Since we are using danhper/fish-ssh-agent to manage the SSH agent, we only have to run this once.

Git Submodules

This repo uses a git submodule for:

  • neoed - Neovim configuration (NEO.ED)

If you encounter issues with the submodule:

Initialize/Update Submodule:

cd ~/.dotfiles
git submodule update --init --recursive

Update Submodule to Latest:

cd ~/.dotfiles/neoed/.config/nvim
git pull origin master

# Commit the update
cd ~/.dotfiles
git add neoed
git commit -m "Update neoed submodule"

Clone with Submodule:

git clone --recurse-submodules https://github.com/edheltzel/dotfiles.git ~/.dotfiles

If Submodule is Empty:

git submodule deinit -f neoed/.config/nvim
git submodule update --init --recursive

MacOS↑

I include this website in 01-preferences.sh - it's a great resource to see what the default key repeat rate will be.

WindowServer RAM Leak As of `2024-07` there is a known bug/issue with macOS where the WindowServer will consume CPU and/or Memory. It is annoying. From my experience, this is related to more than one external monitor. My current workaround is to kill the WindowServer on macOS, which logs you out. Once you log back in the WindowServer will be restarted and your RAM usage will be back in normal ranges. This is a workaround until Apple fixes the issue, which will probably never happen. Usage:
  • Open your Terminal
  • run `killws`
  • Log back into your account
Media Control Keys From time to time some of the "nice-to-have" features of MacOS break. An example of this is when the media keys stop working for one reason or another; Google Chrome/WhatsApp/ can and generally hijack the media keys.

To resolve this just run the following command in the terminal:

luanchctl load -w /System/Library/LaunchAgents/com.apple.rcd.plist

This luanchctl will re-enable media key, which in turn will control Spotify πŸ™‚

Ethernet backhaul Run the `flashEthernet` function to "flush" the Ethernet backhaul.
flashEthernet; and echo 'Ethernet backhaul flushed'
speedtest

TODOs ↑

see github issues

  • ~~Convert fish functions to zsh functions
  • zsh completions seem to be broken issue #40
  • Consider using Home Manager for package management.
    • Look into zsh-completions vs autocomplete
  • include zsh abbreviations
  • Create a single-line install script to execute bootstrap.sh
  • use makefile to execute bootstrap.sh and install.sh
  • Unify bootstrap.sh + install.sh into a single subcommand-driven script
  • update make unstow to include only the available stow package or all
  • add customizations to LazyVim
    • move neovim config to its own repo
  • Add vscode settings and symlink to dotfiles
  • Add XDG Base Directory support
  • update README
    • include XDG info
    • include Stow info
    • include Make info
    • include New bootstrap process
    • include New install process (makefile)