Switch to chezmoi - blitterated/dotfiles GitHub Wiki

Switch to chezmoi for dotfile management

I'm currently using Gnu Stow as a dotfile manager. It's good, but it's more of a binary link farm manager than a dotfile manager. The requirement for Perl isn't my cup of tea, either, There's too many good Rust utilities these days.

draw.io diagram of my chezmoi files logical layout.

Commit and push local changes in the dotfiles repo

Check on git status

git status
On branch main
Your branch is ahead of 'origin/main' by 4 commits.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   nvim/.config/nvim/init.lua
        modified:   nvim/.config/nvim/lazy-lock.json
        modified:   zsh/.zshrc

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        __lib/
        nvim/.config/nvim/lua/linenumber-colors.lua
        nvim/.config/nvim/lua/plugins/lsp-config.lua

no changes added to commit (use "git add" and/or "git commit -a")

Check the dates on modified and untracked files

ls -l nvim/.config/nvim/init.lua
.rw-r--r--@ 665 blitterated  6 Jun 00:01   nvim/.config/nvim/init.lua
ls -l nvim/.config/nvim/lazy-lock.json
.rw-r--r--@ 1.2k blitterated  7 Jun 15:27   nvim/.config/nvim/lazy-lock.json
ls -l nvim/.config/nvim/lua/linenumber-colors.lua
.rw-r--r--@ 162 blitterated  5 Jun 23:48   nvim/.config/nvim/lua/linenumber-colors.lua
ls -l nvim/.config/nvim/lua/plugins/lsp-config.lua
.rw-r--r--@ 605 blitterated  7 Jun 15:27   nvim/.config/nvim/lua/plugins/lsp-config.lua
ls -l zsh/.zshrc
.rw-r--r--@ 3.3k blitterated 10 Jun 00:42   zsh/.zshrc

Here they are in order.

Date Time File
06/05/2025 23:48 nvim/.config/nvim/lua/linenumber-colors.lua
06/06/2025 00:01 nvim/.config/nvim/init.lua
06/07/2025 15:27 nvim/.config/nvim/lazy-lock.json
06/07/2025 15:27 nvim/.config/nvim/lua/plugins/lsp-config.lua
06/10/2025 00:42 zsh/.zshrc

Iterate towards replacing links with dotfiles

Search for links to the dotfiles repo from the home directory

It took a while with Gnu find, so I also tried timing it.

time find $HOME -type l -exec ls -l {} \; | grep '.dotfiles/'
lrwxr-xr-x@ 1 blitterated  staff  30 May 18 18:32 ./.config/nvim -> ../.dotfiles/nvim/.config/nvim
lrwxr-xr-x@ 1 blitterated  staff  43 May 18 18:37 ./.config/starship.toml -> ../.dotfiles/starship/.config/starship.toml
lrwxr-xr-x@ 1 blitterated  staff  21 May 18 18:38 ./.gemrc -> .dotfiles/ruby/.gemrc
lrwxr-xr-x@ 1 blitterated  staff  25 May 18 18:43 ./.tmux.conf -> .dotfiles/tmux/.tmux.conf
lrwxr-xr-x@ 1 blitterated  staff  22 May 18 18:25 ./.bashrc -> .dotfiles/bash/.bashrc
lrwxr-xr-x@ 1 blitterated  staff  20 May 18 18:28 ./.zshrc -> .dotfiles/zsh/.zshrc
lrwxr-xr-x@ 1 blitterated  staff  34 May 18 22:01 ./.zsh_local -> .dotfiles/zsh_local.mac/.zsh_local
lrwxr-xr-x@ 1 blitterated  staff  23 May 18 18:28 ./.zprofile -> .dotfiles/zsh/.zprofile
lrwxr-xr-x@ 1 blitterated  staff  36 May 18 21:45 ./.bash_local -> .dotfiles/bash_local.mac/.bash_local
lrwxr-xr-x@ 1 blitterated  staff  31 May 18 18:30 ./.gitignore_global -> .dotfiles/git/.gitignore_global
lrwxr-xr-x@ 1 blitterated  staff  20 May 18 18:44 ./.vimrc -> .dotfiles/vim/.vimrc
lrwxr-xr-x@ 1 blitterated  staff  21 May 18 18:38 ./.pryrc -> .dotfiles/ruby/.pryrc
lrwxr-xr-x@ 1 blitterated  staff  28 May 18 18:25 ./.bash_profile -> .dotfiles/bash/.bash_profile
lrwxr-xr-x@ 1 blitterated  staff  22 May 18 18:44 ./.psqlrc -> .dotfiles/psql/.psqlrc
lrwxr-xr-x@ 1 blitterated  staff  24 May 18 18:30 ./.gitconfig -> .dotfiles/git/.gitconfig
lrwxr-xr-x@ 1 blitterated  staff  18 May 18 18:44 ./.bcrc -> .dotfiles/bc/.bcrc

find . -type l -exec ls -l {} \;
35.04s user
84.79s system
41% cpu
4:50.30 total

grep '.dotfiles/'
0.22s user
0.15s system
0% cpu
4:50.30 total

Here's the results in table form

Type Link Target
File ~/.bash_local ~/.dotfiles/bash_local.mac/.bash_local
File ~/.bash_profile ~/.dotfiles/bash/.bash_profile
File ~/.bashrc ~/.dotfiles/bash/.bashrc
File ~/.bcrc ~/.dotfiles/bc/.bcrc
Dir ~/.config/nvim ~/.dotfiles/nvim/.config/nvim
File ~/.config/starship.toml ~/.dotfiles/starship/.config/starship.toml
File ~/.gemrc ~/.dotfiles/ruby/.gemrc
File ~/.gitconfig ~/.dotfiles/git/.gitconfig
File ~/.gitignore_global ~/.dotfiles/git/.gitignore_global
File ~/.pryrc ~/.dotfiles/ruby/.pryrc
File ~/.psqlrc ~/.dotfiles/psql/.psqlrc
File ~/.tmux.conf ~/.dotfiles/tmux/.tmux.conf
File ~/.vimrc ~/.dotfiles/vim/.vimrc
File ~/.zprofile ~/.dotfiles/zsh/.zprofile
File ~/.zsh_local ~/.dotfiles/zsh_local.mac/.zsh_local
File ~/.zshrc ~/.dotfiles/zsh/.zshrc

Get dotfile links into into a bash array

dotlinks=( '.bash_local' '.bash_profile' '.bashrc' '.bcrc' '.config/nvim' '.config/starship.toml'
           '.gemrc' '.gitconfig' '.gitignore_global' '.pryrc' '.psqlrc' '.tmux.conf' '.vimrc'
           '.zprofile' '.zsh_local' '.zshrc' )

Try out the array.

echo "${dotlinks[@]}"
.bash_local .bash_profile .bashrc .bcrc .config/nvim .config/starship.toml .gemrc .gitconfig .gitignore_global .pryrc .psqlrc .tmux.conf .vimrc .zprofile .zsh_local .zshrc

Try out listing the array.

for link in "${dotlinks[@]}"; do echo "\"${link}\""; done
".bash_local"
".bash_profile"
".bashrc"
".bcrc"
".config/nvim"
".config/starship.toml"
".gemrc"
".gitconfig"
".gitignore_global"
".pryrc"
".psqlrc"
".tmux.conf"
".vimrc"
".zprofile"
".zsh_local"
".zshrc"

Transform source and target paths

Look up paths to link targets

for link in "${dotlinks[@]}"; do
  local target=$(readlink "$HOME/$link")
  echo "\"~/${link}\" -> "\"${target}\"
done
"~/.bash_local" -> ".dotfiles/bash_local.mac/.bash_local"
"~/.bash_profile" -> ".dotfiles/bash/.bash_profile"
"~/.bashrc" -> ".dotfiles/bash/.bashrc"
"~/.bcrc" -> ".dotfiles/bc/.bcrc"
"~/.config/nvim" -> "../.dotfiles/nvim/.config/nvim"
"~/.config/starship.toml" -> "../.dotfiles/starship/.config/starship.toml"
"~/.gemrc" -> ".dotfiles/ruby/.gemrc"
"~/.gitconfig" -> ".dotfiles/git/.gitconfig"
"~/.gitignore_global" -> ".dotfiles/git/.gitignore_global"
"~/.pryrc" -> ".dotfiles/ruby/.pryrc"
"~/.psqlrc" -> ".dotfiles/psql/.psqlrc"
"~/.tmux.conf" -> ".dotfiles/tmux/.tmux.conf"
"~/.vimrc" -> ".dotfiles/vim/.vimrc"
"~/.zprofile" -> ".dotfiles/zsh/.zprofile"
"~/.zsh_local" -> ".dotfiles/zsh_local.mac/.zsh_local"
"~/.zshrc" -> ".dotfiles/zsh/.zshrc"

Generate full path to target

Looks like the path returned by readLink returns the path relative to the target fromt the soft link file. Scrape off everything from the start of the target path up to and including .dotfiles/, and then stitch together a full path to the file.

for link in "${dotlinks[@]}"; do
  local target="${HOME}/.dotfiles/$(readlink "$HOME/$link" | gsed -E -e "s/^.*?\.dotfiles\///")"
  echo "\"~/${link}\" -> "\"${target}\"
done
"~/.bash_local" -> "/Users/blitterated/.dotfiles/bash_local.mac/.bash_local"
"~/.bash_profile" -> "/Users/blitterated/.dotfiles/bash/.bash_profile"
"~/.bashrc" -> "/Users/blitterated/.dotfiles/bash/.bashrc"
"~/.bcrc" -> "/Users/blitterated/.dotfiles/bc/.bcrc"
"~/.config/nvim" -> "/Users/blitterated/.dotfiles/nvim/.config/nvim"
"~/.config/starship.toml" -> "/Users/blitterated/.dotfiles/starship/.config/starship.toml"
"~/.gemrc" -> "/Users/blitterated/.dotfiles/ruby/.gemrc"
"~/.gitconfig" -> "/Users/blitterated/.dotfiles/git/.gitconfig"
"~/.gitignore_global" -> "/Users/blitterated/.dotfiles/git/.gitignore_global"
"~/.pryrc" -> "/Users/blitterated/.dotfiles/ruby/.pryrc"
"~/.psqlrc" -> "/Users/blitterated/.dotfiles/psql/.psqlrc"
"~/.tmux.conf" -> "/Users/blitterated/.dotfiles/tmux/.tmux.conf"
"~/.vimrc" -> "/Users/blitterated/.dotfiles/vim/.vimrc"
"~/.zprofile" -> "/Users/blitterated/.dotfiles/zsh/.zprofile"
"~/.zsh_local" -> "/Users/blitterated/.dotfiles/zsh_local.mac/.zsh_local"
"~/.zshrc" -> "/Users/blitterated/.dotfiles/zsh/.zshrc"

View full file path in Markdown table

Dump the above in a Markdown table.

echo '| Source | Target |'
echo '| ------ | ------ |'
for link in "${dotlinks[@]}"; do
  local target="${HOME}/.dotfiles/$(readlink "$HOME/$link" | gsed -E -e "s/^.*?\.dotfiles\///")"
  echo "| \`~/${link}\` | \`${target}\` |"
done
Source Target
~/.bash_local /Users/blitterated/.dotfiles/bash_local.mac/.bash_local
~/.bash_profile /Users/blitterated/.dotfiles/bash/.bash_profile
~/.bashrc /Users/blitterated/.dotfiles/bash/.bashrc
~/.bcrc /Users/blitterated/.dotfiles/bc/.bcrc
~/.config/nvim /Users/blitterated/.dotfiles/nvim/.config/nvim
~/.config/starship.toml /Users/blitterated/.dotfiles/starship/.config/starship.toml
~/.gemrc /Users/blitterated/.dotfiles/ruby/.gemrc
~/.gitconfig /Users/blitterated/.dotfiles/git/.gitconfig
~/.gitignore_global /Users/blitterated/.dotfiles/git/.gitignore_global
~/.pryrc /Users/blitterated/.dotfiles/ruby/.pryrc
~/.psqlrc /Users/blitterated/.dotfiles/psql/.psqlrc
~/.tmux.conf /Users/blitterated/.dotfiles/tmux/.tmux.conf
~/.vimrc /Users/blitterated/.dotfiles/vim/.vimrc
~/.zprofile /Users/blitterated/.dotfiles/zsh/.zprofile
~/.zsh_local /Users/blitterated/.dotfiles/zsh_local.mac/.zsh_local
~/.zshrc /Users/blitterated/.dotfiles/zsh/.zshrc

Separate link filename from path and use $HOME instead of ~

Switch from using ~ to ${HOME} for the link's full path. Also, separate the path from the filename.

echo '| Source Path | Source Filename | Target Full Path |'
echo '| ------ | ------ | ------ |'
for link in "${dotlinks[@]}"; do
  local source="${HOME}/${link}"
  local source_path="$(dirname "${source}")"
  local source_filename="$(basename "${source}")"
  local target="${HOME}/.dotfiles/$(readlink "$HOME/$link" | gsed -E -e "s/^.*?\.dotfiles\///")"
  echo "| \`${source_path}\` | \`${source_filename}\` | \`${target}\` |"
done
Source Path Source Filename Target Full Path
/Users/blitterated .bash_local /Users/blitterated/.dotfiles/bash_local.mac/.bash_local
/Users/blitterated .bash_profile /Users/blitterated/.dotfiles/bash/.bash_profile
/Users/blitterated .bashrc /Users/blitterated/.dotfiles/bash/.bashrc
/Users/blitterated .bcrc /Users/blitterated/.dotfiles/bc/.bcrc
/Users/blitterated/.config nvim /Users/blitterated/.dotfiles/nvim/.config/nvim
/Users/blitterated/.config starship.toml /Users/blitterated/.dotfiles/starship/.config/starship.toml
/Users/blitterated .gemrc /Users/blitterated/.dotfiles/ruby/.gemrc
/Users/blitterated .gitconfig /Users/blitterated/.dotfiles/git/.gitconfig
/Users/blitterated .gitignore_global /Users/blitterated/.dotfiles/git/.gitignore_global
/Users/blitterated .pryrc /Users/blitterated/.dotfiles/ruby/.pryrc
/Users/blitterated .psqlrc /Users/blitterated/.dotfiles/psql/.psqlrc
/Users/blitterated .tmux.conf /Users/blitterated/.dotfiles/tmux/.tmux.conf
/Users/blitterated .vimrc /Users/blitterated/.dotfiles/vim/.vimrc
/Users/blitterated .zprofile /Users/blitterated/.dotfiles/zsh/.zprofile
/Users/blitterated .zsh_local /Users/blitterated/.dotfiles/zsh_local.mac/.zsh_local
/Users/blitterated .zshrc /Users/blitterated/.dotfiles/zsh/.zshrc

Test copying target files and directories

Most of the dotfile links created by stow point at a file. However, Neovim's configuration files are pointed to with a directory link.

Create a test folder for cp tests.

mkdir test_cp && cd $_

Directory links

Test out a recursive copy to a test folder.

cp -r ~/.dotfiles/nvim/.config/nvim .
tree
nvim
├── init.lua
├── lazy-lock.json
└── lua
    ├── config
    │   └── lazy.lua
    ├── linenumber-colors.lua
    ├── plugins
    │   ├── catppuccin.lua
    │   ├── lsp-config.lua
    │   ├── lualine.lua
    │   ├── neotree.lua
    │   ├── telescope.lua
    │   └── treesitter.lua
    └── vim-options.lua

File links

Can we use the same cp -r command as above to copy files? This would avoid needing a decision branch in bash.

cp -r ~/.dotfiles/bc/.bcrc .
tree -a
.bcrc
nvim
├── init.lua
├── lazy-lock.json
└── lua
    ├── config
    │   └── lazy.lua
    ├── linenumber-colors.lua
    ├── plugins
    │   ├── catppuccin.lua
    │   ├── lsp-config.lua
    │   ├── lualine.lua
    │   ├── neotree.lua
    │   ├── telescope.lua
    │   └── treesitter.lua
    └── vim-options.lua

Double check generating rm and cp commands

echo "| Delete link | Copy dotfiles |"
echo "| ----------- | ------------- |"
for link in "${dotlinks[@]}"; do
  local source="${HOME}/${link}"
  local source_path="$(dirname "${source}")"
  local target="${HOME}/.dotfiles/$(readlink "$HOME/$link" | gsed -E -e "s/^.*?\.dotfiles\///")"

  local rm_cmd="rm \"${source}\""
  local cp_cmd="cp -r \"${target}\" \"${source_path}\""

  echo "| ${rm_cmd} | ${cp_cmd} |"
done
Delete link Copy dotfiles
rm "/Users/blitterated/.bash_local" cp -r "/Users/blitterated/.dotfiles/bash_local.mac/.bash_local" "/Users/blitterated"
rm "/Users/blitterated/.bash_profile" cp -r "/Users/blitterated/.dotfiles/bash/.bash_profile" "/Users/blitterated"
rm "/Users/blitterated/.bashrc" cp -r "/Users/blitterated/.dotfiles/bash/.bashrc" "/Users/blitterated"
rm "/Users/blitterated/.bcrc" cp -r "/Users/blitterated/.dotfiles/bc/.bcrc" "/Users/blitterated"
rm "/Users/blitterated/.config/nvim" cp -r "/Users/blitterated/.dotfiles/nvim/.config/nvim" "/Users/blitterated/.config"
rm "/Users/blitterated/.config/starship.toml" cp -r "/Users/blitterated/.dotfiles/starship/.config/starship.toml" "/Users/blitterated/.config"
rm "/Users/blitterated/.gemrc" cp -r "/Users/blitterated/.dotfiles/ruby/.gemrc" "/Users/blitterated"
rm "/Users/blitterated/.gitconfig" cp -r "/Users/blitterated/.dotfiles/git/.gitconfig" "/Users/blitterated"
rm "/Users/blitterated/.gitignore_global" cp -r "/Users/blitterated/.dotfiles/git/.gitignore_global" "/Users/blitterated"
rm "/Users/blitterated/.pryrc" cp -r "/Users/blitterated/.dotfiles/ruby/.pryrc" "/Users/blitterated"
rm "/Users/blitterated/.psqlrc" cp -r "/Users/blitterated/.dotfiles/psql/.psqlrc" "/Users/blitterated"
rm "/Users/blitterated/.tmux.conf" cp -r "/Users/blitterated/.dotfiles/tmux/.tmux.conf" "/Users/blitterated"
rm "/Users/blitterated/.vimrc" cp -r "/Users/blitterated/.dotfiles/vim/.vimrc" "/Users/blitterated"
rm "/Users/blitterated/.zprofile" cp -r "/Users/blitterated/.dotfiles/zsh/.zprofile" "/Users/blitterated"
rm "/Users/blitterated/.zsh_local" cp -r "/Users/blitterated/.dotfiles/zsh_local.mac/.zsh_local" "/Users/blitterated"
rm "/Users/blitterated/.zshrc" cp -r "/Users/blitterated/.dotfiles/zsh/.zshrc" "/Users/blitterated"

Looks good!

Replace the Links

Capture listing of current linked dotfiles

dotlinks=( '.bash_local' '.bash_profile' '.bashrc' '.bcrc' '.config/nvim' '.config/starship.toml'
           '.gemrc' '.gitconfig' '.gitignore_global' '.pryrc' '.psqlrc' '.tmux.conf' '.vimrc'
           '.zprofile' '.zsh_local' '.zshrc' )

ls -la $dotlinks[@]
lrwxr-xr-x@ - blitterated 18 May 21:45 .bash_local -> .dotfiles/bash_local.mac/.bash_local
lrwxr-xr-x@ - blitterated 18 May 18:25 .bash_profile -> .dotfiles/bash/.bash_profile
lrwxr-xr-x@ - blitterated 18 May 18:25 .bashrc -> .dotfiles/bash/.bashrc
lrwxr-xr-x@ - blitterated 18 May 18:44 .bcrc -> .dotfiles/bc/.bcrc
lrwxr-xr-x@ - blitterated 18 May 18:38 .gemrc -> .dotfiles/ruby/.gemrc
lrwxr-xr-x@ - blitterated 18 May 18:30 .gitconfig -> .dotfiles/git/.gitconfig
lrwxr-xr-x@ - blitterated 18 May 18:30 .gitignore_global -> .dotfiles/git/.gitignore_global
lrwxr-xr-x@ - blitterated 18 May 18:38 .pryrc -> .dotfiles/ruby/.pryrc
lrwxr-xr-x@ - blitterated 18 May 18:44 .psqlrc -> .dotfiles/psql/.psqlrc
lrwxr-xr-x@ - blitterated 18 May 18:43 .tmux.conf -> .dotfiles/tmux/.tmux.conf
lrwxr-xr-x@ - blitterated 18 May 18:44 .vimrc -> .dotfiles/vim/.vimrc
lrwxr-xr-x@ - blitterated 18 May 18:28 .zprofile -> .dotfiles/zsh/.zprofile
lrwxr-xr-x@ - blitterated 18 May 22:01 .zsh_local -> .dotfiles/zsh_local.mac/.zsh_local
lrwxr-xr-x@ - blitterated 18 May 18:28 .zshrc -> .dotfiles/zsh/.zshrc
lrwxr-xr-x@ - blitterated 18 May 18:37 .config/starship.toml -> ../.dotfiles/starship/.config/starship.toml

.config/nvim:
drwxr-xr-x@    - blitterated 18 May 18:31 ..
drwxr-xr-x@    - blitterated  5 Jun 23:59 lua
lrwxr-xr-x@    - blitterated 18 May 18:32 . -> ../.dotfiles/nvim/.config/nvim
.rw-r--r--@  665 blitterated  6 Jun 00:01 init.lua
.rw-r--r--@ 1.2k blitterated  7 Jun 15:27 lazy-lock.json

Execute Replacement

cat << EOF > replace_stow_links.sh
#!/bin/bash

replace_stow_links ()
{
  # The dotfile links to be replaced
  dotlinks=( '.bash_local' '.bash_profile' '.bashrc' '.bcrc' '.config/nvim' '.config/starship.toml'
             '.gemrc' '.gitconfig' '.gitignore_global' '.pryrc' '.psqlrc' '.tmux.conf' '.vimrc'
             '.zprofile' '.zsh_local' '.zshrc' )

  # Backup directory for current link files
  dead_dots="${HOME}/.dead_dots"
  mkdir -p "${dead_dots}"

  # Iterate dotfile links, backup, and replace them.
  for link in "${dotlinks[@]}"; do
    local source_link="${HOME}/${link}"
    local source_path="$(dirname "${source_link}")"
    local target="${HOME}/.dotfiles/$(readlink "$HOME/$link" | gsed -E -e "s/^.*?\.dotfiles\///")"

    cp -P "${source_link}" "${dead_dots}"

    echo
    echo "Replacing \"${source_link}\" with \"${target}\""

    rm "${source_link}"
    cp -r "${target}" "${source_path}"
  done
}
EOF

chmod +x replace_stow_links.sh

./replace_stow_links.sh

Final check

dotlinks=( '.bash_local' '.bash_profile' '.bashrc' '.bcrc' '.config/nvim' '.config/starship.toml'
           '.gemrc' '.gitconfig' '.gitignore_global' '.pryrc' '.psqlrc' '.tmux.conf' '.vimrc'
           '.zprofile' '.zsh_local' '.zshrc' )

ls -la $dotlinks[@]
.rw-r--r--@ 1.6k blitterated  6 Jul 17:14 .bash_local
.rw-r--r--@   76 blitterated  6 Jul 17:14 .bash_profile
.rw-r--r--@ 2.6k blitterated  6 Jul 17:14 .bashrc
.rw-r--r--@  114 blitterated  6 Jul 17:14 .bcrc
.rw-r--r--@   51 blitterated  6 Jul 17:14 .gemrc
.rw-r--r--@  115 blitterated  6 Jul 17:14 .gitconfig
.rw-r--r--@   24 blitterated  6 Jul 17:14 .gitignore_global
.rw-r--r--@   66 blitterated  6 Jul 17:14 .pryrc
.rw-r--r--@  987 blitterated  6 Jul 17:14 .psqlrc
.rw-r--r--@ 1.8k blitterated  6 Jul 17:14 .tmux.conf
.rw-r--r--@ 2.5k blitterated  6 Jul 17:14 .vimrc
.rw-r--r--@    0 blitterated  6 Jul 17:14 .zprofile
.rw-r--r--@ 2.1k blitterated  6 Jul 17:14 .zsh_local
.rw-r--r--@ 2.2k blitterated  6 Jul 17:14 .zshrc
.rw-r--r--@ 3.8k blitterated  6 Jul 17:14 .config/starship.toml

.config/nvim:
drwxr-xr-x@    - blitterated  6 Jul 17:14 .
drwx------     - blitterated  6 Jul 17:14 ..
drwxr-xr-x@    - blitterated  6 Jul 17:14 lua
.rw-r--r--@  665 blitterated  6 Jul 17:14 init.lua
.rw-r--r--@ 1.2k blitterated  6 Jul 17:14 lazy-lock.json

Double check ~/.config/nvim

tree -a .config/nvim
drwxr-xr-x@    - blitterated  6 Jul 17:14 .config/nvim
.rw-r--r--@  665 blitterated  6 Jul 17:14 ├── init.lua
.rw-r--r--@ 1.2k blitterated  6 Jul 17:14 ├── lazy-lock.json
drwxr-xr-x@    - blitterated  6 Jul 17:14 └── lua
drwxr-xr-x@    - blitterated  6 Jul 17:14     ├── config
.rw-r--r--@ 1.3k blitterated  6 Jul 17:14     │   └── lazy.lua
.rw-r--r--@  162 blitterated  6 Jul 17:14     ├── linenumber-colors.lua
drwxr-xr-x@    - blitterated  6 Jul 17:14     ├── plugins
.rw-r--r--@  155 blitterated  6 Jul 17:14     │   ├── catppuccin.lua
.rw-r--r--@  605 blitterated  6 Jul 17:14     │   ├── lsp-config.lua
.rw-r--r--@  162 blitterated  6 Jul 17:14     │   ├── lualine.lua
.rw-r--r--@  413 blitterated  6 Jul 17:14     │   ├── neotree.lua
.rw-r--r--@  303 blitterated  6 Jul 17:14     │   ├── telescope.lua
.rw-r--r--@  591 blitterated  6 Jul 17:14     │   └── treesitter.lua
.rw-r--r--@  540 blitterated  6 Jul 17:14     └── vim-options.lua

Looks good!

Prepare dotfiles repo

Tag latest commit

Tag last commit as "Final Gnu Stow Commit".

git tag -a gnu-stow-final -m "This is the last Gnu Stow commit before the switch to chezmoi."

Push tag to origin.

git push --tags

Move current dotfiles repo

chezmoi expects the dotfiles repo to reside at ~/.local/share/chezmoi. Move ~/.dofiles to that location.

mv ~/.dotfiles ~/.local/share
cd ~/.local/share
mv .dotfiles chezmoi

Create an orphan branch

This branch will omit

WAIT!!!! I DON'T WANT AN ORPHAN BRANCH!!!! I want to maintain the history of the files. Shit, this is gonna be a lot more work.

Create a branch for chezmoi

git checkout -b chezmoi

Transform dotfiles file hierarchy for chezmoi

Dotfile repo's current Gnu Stow structure

tree -a -I .git
.
├── .gitignore
├── __lib
│   └── source_files.sh
├── bash
│   ├── .bash_profile
│   └── .bashrc
├── bash_local.mac
│   └── .bash_local
├── bc
│   └── .bcrc
├── git
│   ├── .gitconfig
│   └── .gitignore_global
├── nvim
│   └── .config
│       └── nvim
│           ├── init.lua
│           ├── lazy-lock.json
│           └── lua
│               ├── config
│               │   └── lazy.lua
│               ├── linenumber-colors.lua
│               ├── plugins
│               │   ├── catppuccin.lua
│               │   ├── lsp-config.lua
│               │   ├── lualine.lua
│               │   ├── neotree.lua
│               │   ├── telescope.lua
│               │   └── treesitter.lua
│               └── vim-options.lua
├── psql
│   └── .psqlrc
├── README.md
├── ruby
│   ├── .gemrc
│   └── .pryrc
├── starship
│   └── .config
│       └── starship.toml
├── tmux
│   └── .tmux.conf
├── vim
│   └── .vimrc
├── Windows
│   ├── link_powershell_configs.ps1
│   └── PowerShell
│       └── Microsoft.PowerShell_profile.ps1
├── zsh
│   ├── .zprofile
│   └── .zshrc
└── zsh_local.mac
    └── .zsh_local

Repo transformation script

# .gitignore                    do nothing
# __lib/source_files.sh         do nothing

git mv bash/.bash_profile ./dot_bash_profile
git mv bash/.bashrc ./dot_bashrc
rm -rf bash

git mv bash_local.mac/.bash_local ./dot_bash_local
rm -rf bash_local.mac

git mv bc/.bcrc ./dot_bcrc
rm -rf bc

git mv git/.gitconfig ./dot_gitconfig
git mv git/.gitignore_global ./dot_gitignore_global
rm -rf git

git mv nvim/.config ./dot_config
rm -rf nvim

git mv psql/.psqlrc ./dot_psqlrc
rm -rf psql

# README.md                     do nothing

git mv ruby/.gemrc ./dot_gemrc
git mv ruby/.pryrc ./dot_pryrc
rm -rf ruby

git mv starship/.config/starship.toml ./dot_config/starship.toml
rm -rf starship

git mv tmux/.tmux.conf ./dot_tmux.conf
rm -rf tmux

git mv vim/.vimrc ./dot_vimrc
rm -rf vim

# Windows/link_powershell_configs.ps1                   save for later
# Windows/PowerShell/Microsoft.PowerShell_profile.ps1   save for later

git mv zsh/.zprofile ./dot_zprofile
git mv zsh/.zshrc ./dot_zshrc
rm -rf zsh

git mv zsh_local.mac/.zsh_local ./dot_zsh_local
rm -rf zsh_local.mac

Dotfile repo's new chezmoi structure

tree -a -I .git
.
├── .gitignore
├── __lib
│   └── source_files.sh
├── dot_bash_local
├── dot_bash_profile
├── dot_bashrc
├── dot_bcrc
├── dot_config
│   ├── nvim
│   │   ├── init.lua
│   │   ├── lazy-lock.json
│   │   └── lua
│   │       ├── config
│   │       │   └── lazy.lua
│   │       ├── linenumber-colors.lua
│   │       ├── plugins
│   │       │   ├── catppuccin.lua
│   │       │   ├── lsp-config.lua
│   │       │   ├── lualine.lua
│   │       │   ├── neotree.lua
│   │       │   ├── telescope.lua
│   │       │   └── treesitter.lua
│   │       └── vim-options.lua
│   └── starship.toml
├── dot_gemrc
├── dot_gitconfig
├── dot_gitignore_global
├── dot_pryrc
├── dot_psqlrc
├── dot_tmux.conf
├── dot_vimrc
├── dot_zprofile
├── dot_zsh_local
├── dot_zshrc
├── README.md
└── Windows
    ├── link_powershell_configs.ps1
    └── PowerShell
        └── Microsoft.PowerShell_profile.ps1

Set up chezmoi

Copy current dotfiles in $HOME to a backup folder

mkdir ~/.dotbak
cd ~/.dotbak
mkdir ./.config

cp ~/.bash_local .
cp ~/.bash_profile .
cp ~/.bashrc .
cp ~/.bcrc .
cp ~/.gemrc .
cp ~/.gitconfig .
cp ~/.gitignore_global .
cp ~/.pryrc .
cp ~/.psqlrc .
cp ~/.tmux.conf .
cp ~/.vimrc .
cp ~/.zprofile .
cp ~/.zsh_local .
cp ~/.zshrc .
cp -r ~/.config/nvim ./.config
cp ~/.config/starship.toml ./.config

cd -

Restore script, JIC

cd ~/.dotbak

cp .bash_local ~
cp .bash_profile ~
cp .bashrc ~
cp .bcrc ~
cp .gemrc ~
cp .gitconfig ~
cp .gitignore_global ~
cp .pryrc ~
cp .psqlrc ~
cp .tmux.conf ~
cp .vimrc ~
cp .zprofile ~
cp .zsh_local ~
cp .zshrc ~
cp -r ~/.config/nvim ~/.config
cp .config/starship.toml ~/.config

cd -

List modified dates for dotfiles in $HOME dir

Capture a listing of current dotfiles in $HOME for comparison after applying with chezmoi.

I have ls is aliased to eza.

The following eza options are used in the steps below:

switch long option description
-1 --oneline display one entry per line
-a --all show hidden and 'dot' files. Use this twice to also show the '.' and '..' directories
-l --long display extended file metadata as a table
-R --recurse recurse into directories
-I GLOBS --ignore-glob GLOBS glob patterns (pipe-separated) of files to ignore
-f --only-files list only files
-m --modified use the modified timestamp field
  --absolute display entries with their absolute path (on, follow, off)
  --time-style how to format timestamps (default, iso, long-iso, full-iso, relative, or a custom style '+' like '+%Y-%m-%d %H:%M')
  --icons=WHEN cisplay icons next to file names. WHEN = always, automatic (auto), never

Create an array of dotfile names

I'm just reusing the dotfile backup dir created above to create this array.

watch_dots=( $(ls -1afR --absolute=on --icons=never ~/.dotbak | grep -Ev "(^$|:)" | gsed -E "s|${HOME}/.dotbak/||") )
echo $watch_dots[@]
.bash_local
.bash_profile
.bashrc
.bcrc
.gemrc
.gitconfig
.gitignore_global
.pryrc
.psqlrc
.tmux.conf
.vimrc
.zprofile
.zsh_local
.zshrc
.config/starship.toml
.config/nvim/init.lua
.config/nvim/lazy-lock.json
.config/nvim/lua/linenumber-colors.lua
.config/nvim/lua/vim-options.lua
.config/nvim/lua/config/lazy.lua
.config/nvim/lua/plugins/catppuccin.lua
.config/nvim/lua/plugins/lsp-config.lua
.config/nvim/lua/plugins/lualine.lua
.config/nvim/lua/plugins/neotree.lua
.config/nvim/lua/plugins/telescope.lua
.config/nvim/lua/plugins/treesitter.lua

Get the listing

watch_dots=( $(ls -1afR --absolute=on --icons=never $HOME/.dotbak | grep -Ev "(^$|:)" | gsed -E "s|${HOME}/.dotbak/||") )

ls -1aflmR --absolute=on  --icons=never --time-style '+%Y-%m-%d %H:%M:%S' $watch_dots[@] | grep '^\.[r-]' | gsed -E "s/\.[rwx-]+?@\s+[0-9.k]+\s${USER}\s//"
2025-07-06 17:14:57 /Users/blitterated/.bash_local
2025-07-06 17:14:57 /Users/blitterated/.bash_profile
2025-07-06 17:14:57 /Users/blitterated/.bashrc
2025-07-06 17:14:57 /Users/blitterated/.bcrc
2025-07-06 17:14:57 /Users/blitterated/.gemrc
2025-07-06 17:14:57 /Users/blitterated/.gitconfig
2025-07-06 17:14:57 /Users/blitterated/.gitignore_global
2025-07-06 17:14:57 /Users/blitterated/.pryrc
2025-07-06 17:14:57 /Users/blitterated/.psqlrc
2025-07-06 17:14:57 /Users/blitterated/.tmux.conf
2025-07-06 17:14:57 /Users/blitterated/.vimrc
2025-07-06 17:14:57 /Users/blitterated/.zprofile
2025-07-06 17:14:57 /Users/blitterated/.zsh_local
2025-07-06 17:14:57 /Users/blitterated/.zshrc
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lua/plugins/catppuccin.lua
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/init.lua
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lazy-lock.json
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lua/config/lazy.lua
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lua/linenumber-colors.lua
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lua/plugins/lsp-config.lua
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lua/plugins/lualine.lua
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lua/plugins/neotree.lua
2025-07-06 17:14:57 /Users/blitterated/.config/starship.toml
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lua/plugins/telescope.lua
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lua/plugins/treesitter.lua
2025-07-06 17:14:57 /Users/blitterated/.config/nvim/lua/vim-options.lua

Install chezmoi on MBP

brew install chezmoi

Configure chezmoi on MBP

Ignore folders and files

The following files and directories need to be ignored for various reasons.

  • __lib/source_files.sh - intended to be used with stow
  • Windows/**/* - temporary while I work out the Mac setup first
  • Switch to chezmoi.md - the file you're reading right now

We can use .chezmoiignore to do this.

cat << EOF > .chezmoiignore
README.md               # don't need this in $HOME
Switch to chezmoi.md    # don't need this in $HOME
Windows/                # ignore Windows folder
Windows/**              # ignore the contents of Windows
__lib/                  # ignore __lib folder
__lib/**                # ignore the contents of __lib
EOF

Config file

Variables, a.k.a. configuration settings

[edit]
    command = "nvim"

[git]
    autoCommit = false
    autoPush = false

Convert to Templates

Edit and templatize .bashrc

Make .bashrc a template file and weave in the mac settings

Convert to template.

git mv dot_bashrc dot_bashrc.tmpl

Open the file to edit it.

chezmoi edit .bashrc
chezmoi: .bashrc: not managed

Uh oh. Time to commit everything and try initializing chezmoi again using a URL.

INTERLUDE: Reinitialize chezmoi using source repo URL

Backup the current source repo.

mv $HOME/.local/share/chezmoi $HOME/.local/share/chezmoi.oops

Initialize chezmoi with the upstream source repo URL.

chezmoi -v init --branch chezmoi ghblit:blitterated/dotfiles.git
Cloning into '/Users/blitterated/.local/share/chezmoi'...
remote: Enumerating objects: 245, done.
remote: Counting objects: 100% (245/245), done.
remote: Compressing objects: 100% (124/124), done.
remote: Total 245 (delta 91), reused 225 (delta 71), pack-reused 0 (from 0)
Receiving objects: 100% (245/245), 44.57 KiB | 1.11 MiB/s, done.
Resolving deltas: 100% (91/91), done.

tree showed that $HOME/.local/share/chezmoi and $HOME/.local/share/chezmoi.oops were the same except for .DS_Store in the latter.

See the Appendix entry for details.

Try to edit .bashrc again.

chezmoi edit .bashrc
chezmoi: .bashrc: not managed

AAAAANNNNNNNDDDDDD... I'm an idiot. It works if you're in $HOME or specify $HOME in the command.

chezmoi edit ~/.bashrc

Unnecessary rework. Yay. Make sure you specify the path to the target file and not just its name, e.g. ~/.bashrc and not just .bashrc.

Edit .bashrc template to bring in Mac settings

Open the file to edit it.

chezmoi edit ~/.bashrc

I think chezmoi's mapping from .bashrc to dot_bashrc breaks filetype detection. Force it with this:

:setf bash

Replace any code loading .bash_local with the following template code:

{{- if eq .chezmoi.os "darwin" -}}
{{-   include bash_local/bash_darwin -}}
{{- end -}}

Move dot_bash_local to bash_local/bash_darwin

I plan to use bash_local as the folder for bash local machine configs. The current .bash_local file is specific to a Macbook Pro.

chezmoi cd
mkdir bash_local
 git mv dot_bash_local bash_local/bash_darwin

Test the .bashrc template

chezmoi execute-template < dot_bashrc.tmpl
chezmoi: template: stdin:105: bad character U+002F '/'

Blerg. Line 105 is this line from the template code added above:

{{-   include bash_local/bash_darwin -}}

Turns out it just needed quotes around the file path and it works.

{{-   include "bash_local/bash_darwin" -}}

Apply a verbose dry run

chezmoi apply --dry-run --verbose
I forgot to capture the output :'(

Crap, the .chezmoiignore file isn't being respected by chezmoi apply.

Delete the unwanted crap.

rm README.md
rm Switch\ to\ chezmoi.md
rm -rf Windows/
rm -rf __lib

I removed all the comments and empty lines, and it seems to work now. Here's the contents:

README.md
Switch to chezmoi.md
Windows/
Windows/**
__lib/
__lib/**

BUT, it looks like the bash_local folder and its files need to be ignored.

Update .chezmoiignore with the following:

bash_local/
bash_local/**

Bob's your uncle!

Using .chezmoiroot and .chezmoitemplates

Chages made:

  • Created .chezmoiroot file in repo root whose contents is simply "home".
  • Created home directory in repo root and moved source directory files except for templates into it.
  • Created .chezmoitemplates in repo root and moved templates into it.

Templating broke after doing the above.

Triage

Tried using include action.
cz execute-template < home/dot_bashrc.tmpl
chezmoi: template: stdin:10:3: executing "stdin" at <include "shell_common/bash_zsh_common.sh">: error calling include: open /Users/blitterated/.local/share/chezmoi/home/shell_common/bash_zsh_common.sh: no such file or directory
Tried using the template action.
cz execute-template < home/dot_bashrc.tmpl
chezmoi: template: stdin:10:12: executing "stdin" at <{{template "shell_common/bash_zsh_common.sh"}}>: template "shell_common/bash_zsh_common.sh" not defined
Tried moving the bash_zsh_common.sh template to the root of .chezmoitemplates.
mv .chezmoitemplates/shell_common/bash_zsh_common.sh .chezmoitemplates/bash_zsh_common.sh
cz execute-template < home/dot_bashrc.tmpl
chezmoi: template: stdin:10:12: executing "stdin" at <{{template "shell_common/bash_zsh_common.sh"}}>: template "shell_common/bash_zsh_common.sh" not defined
Moved template back, tried using includeTemplate action.
mv .chezmoitemplates/bash_zsh_common.sh .chezmoitemplates/shell_common/bash_zsh_common.sh
cz execute-template < home/dot_bashrc.tmpl
chezmoi: template: stdin:10:3: executing "stdin" at <includeTemplate "shell_common/bash_zsh_common.sh">: error calling includeTemplate: open /Users/blitterated/.local/share/chezmoi/home/shell_common/bash_zsh_common.sh: no such file or directory
Tried moving .chezmoitemplates from repo root into home source dir
mv .chezmoitemplates home/
cz execute-template < home/dot_bashrc.tmpl
chezmoi: template: stdin:18:5: executing "stdin" at <includeTemplate "machine_local/macOS/bashrc.tmpl">: error calling includeTemplate: machine_local/macOS/bashrc.tmpl:9:3: executing "machine_local/macOS/bashrc.tmpl" at <include "machine_local/macOS/bash_zsh_common.sh">: error calling include: open /Users/blitterated/.local/share/chezmoi/home/machine_local/macOS/bash_zsh_common.sh: no such file or directory
Switched from include action to includeTemplate action in home/.chezmoitemplates/machine_local/macOS/bashrc.tmpl.
cz execute-template < home/dot_bashrc.tmpl
#   ▄ ▄▖▄▖▖▖▄▖▄▖
#   ▙▘▌▌▚ ▙▌▙▘▌
# ▗ ▙▘▛▌▄▌▌▌▌▌▙▖


########################################
# Common bash + zsh config             #
########################################
...

GREAT SUCCESS!!!

Used chezmoi apply to find and fix the rest of the templating errors.

They were all changes from the include action to the includeTemplate action.

cz apply
chezmoi: .zshrc: template: dot_zshrc.tmpl:10:3: executing "dot_zshrc.tmpl" at <include "shell_common/bash_zsh_common.sh">: error calling include: open /Users/blitterated/.local/share/chezmoi/home/shell_common/bash_zsh_common.sh: no such file or directory
cz apply
chezmoi: .zshrc: template: dot_zshrc.tmpl:35:5: executing "dot_zshrc.tmpl" at <includeTemplate "machine_local/macOS/zshrc.tmpl">: error calling includeTemplate: machine_local/macOS/zshrc.tmpl:9:3: executing "machine_local/macOS/zshrc.tmpl" at <include "machine_local/macOS/bash_zsh_common.sh">: error calling include: open /Users/blitterated/.local/share/chezmoi/home/machine_local/macOS/bash_zsh_common.sh: no such file or directory
cz apply
chezmoi.toml has changed since chezmoi last wrote it? diff/overwrite/all-overwrite/skip/quit
bat home/chezmoi.toml
───────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: home/chezmoi.toml
───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ [edit]
   2   │     command = "nvim"
   3   │
   4   │ [git]
   5   │     autoCommit = false
   6   │     autoPush = false
───────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cz apply
diff --git a/chezmoi.toml b/chezmoi.toml
new file mode 100644
index 0000000000000000000000000000000000000000..6dda1421433b89aa136817e1b4fb4e34b8712656
--- /dev/null
+++ b/chezmoi.toml
@@ -0,0 +1,6 @@
+[edit]
+    command = "nvim"
+
+[git]
+    autoCommit = false
+    autoPush = false

git status didn't see any difference. 🤷

.chezmoitemplates did not get written to ~.

ls ~/.chezmoitemplates
"/Users/blitterated/.chezmoitemplates": No such file or directory (os error 2)

GREAT SUCCESS!!!

Add and templatize Karabiner's mbp_only_complex_mods.json

Full path: ~/.config/karabiner/assets/complex_modifications/mbp_only_complex_mods.json

Add mbp_only_complex_mods.json as a template to the source directory

Add mbp_only_complex_mods.json as a template, first try

karabiner_complex_config="~/.config/karabiner/assets/complex_modifications/mbp_only_complex_mods.json"
chezmoi add --template "${karabiner_complex_config}"
chezmoi chattr +template "${karabiner_complex_config}"

Trying to add the config with chezmoi add gave the following error:

chezmoi: /Users/blitterated/.local/share/chezmoi: cannot add chezmoi file to chezmoi (/Users/blitterated/.local/share/chezmoi is protected)

Apparently, you have to add each folder in ~/.config manually.

Recreate the path in the source directory for the target file

Target file:

karabiner_complex_config="~/.config/karabiner/assets/complex_modifications/mbp_only_complex_mods.json"

Grab the dirname and replace ~/.config above with ~/.local/share/chezmoi/home/dot_config/.

orig_cfg_path="$( dirname "${karabiner_complex_config}" )"
dir_in_home="~/.config"
dir_in_chezmoi="~/.local/share/chezmoi/home/dot_config"
sed "s|${dir_in_home}|${dir_in_chezmoi}|g" <<< "${orig_cfg_path}"
~/.local/share/chezmoi/home/dot_config/karabiner/assets/complex_modifications

Compare the original directory and the new path for chezmoi's source directory.

original:                                ~/.config/karabiner/assets/complex_modifications
chezmoi:    ~/.local/share/chezmoi/home/dot_config/karabiner/assets/complex_modifications

Create the new path.

new_chezmoi_path=$(sed "s|${dir_in_home}|${dir_in_chezmoi}|g" <<< "${orig_cfg_path}")
mkdir -p "${new_chezmoi_path}"

Add mbp_only_complex_mods.json as a template, second try

Now add the Karabiner complex config as a template.

chezmoi add --template "${karabiner_complex_config}"
chezmoi chattr +template "${karabiner_complex_config}"

Not sure why, but a lot of the directories along the path were marked as private.

ls ~/.local/share/chezmoi/home/dot_config/private_karabiner/private_assets/private_complex_modifications/
mbp_only_complex_mods.json.tmpl

Parameterize mbp_only_complex_mods.json template with data and variables

YOU ARE HERE

Find the vendor_id and product_id for built in MacBook keyboards

Check for ids with system_profiler

system_profiler SPUSBDataType

Found a 3rd party USB keyboard, but no "Apple Internal Keyboard / Trackpad"

Karabiner reports the following IDs for a 2016 MacBook Pro M1 builtin keyboard:

{
  "vendor_id": 1452
},
{
  "product_id":835
}

system_profiler reports IDs in hexidecimal, so let's convert the above IDs.

printf "0x%X\n" 1452
0x5AC
printf "0x%X\n" 835
0x343

Dump the output of system_profiler to a file.

system_profiler | tee ~/sysprfl.txt

YOU WERE HERE

Machine to Machine Differences

The chezmoi Docs have the following recommendations.

If you're trying to manage configuration differences between machines in a file, use templates.

If you're wanting to exclude entire files or directories for a given machine, use template code inside of .chezmoiignore.

The latter requires using some ugly conditionals. Since we want to ignore files unless they apply to a specific machine, we need to add an entry to .chezmoiignore when running chezmoi on a different machine. For example:

{{- if ne .chezmoi.os "windows" }}
Documents\WindowsPowerShell         # Ignore Windows PowerShell 5 configuration
Documents\PowerShell                # Ignore Windows PowerShell 7.5 configuration
{{- end }}

Exploring chezmoi's Data

The chezmoi Testing templates docs in the User Guide show a neat way similar to immediate mode for seeing what values are set for keys.

chezmoi execute-template 'hostname: {{ .chezmoi.hostname }}
OS: {{ .chezmoi.os }}
arch: {{ .chezmoi.arch }}'
hostname: blitterated
OS: darwin
arch: arm64

We can also dump all of chezmoi's data in JSON format with the following:

chezmoi data

Here's another way of getting hostname, OS, and architecture using jq.

chezmoi data | jq ".chezmoi.hostname,.chezmoi.os,.chezmoi.arch"
"blitterated"
"darwin"
"arm64"

chezmoi's Variables docs cover available variables and settings,

TODO

  • ✅ Extract common config from dot_bashrc.tmpl and dot_zshrc.tmpl and templatize.
  • ✅ Test cman function.
  • ✅ Add GCP tools to dot_bashrc.tmpl and dot_zshrc.tmpl.
  • ✅ Have setup for specific utils check for utils' existence first. Print an error if not found.
  • ✅ Change shell_local to machine_local.
  • ✅ Move macOS/darwin related config under machine_local/macOS.

Post Migration Follow Ups

Configuration file changes

  • ✅ Swap out nvim for vim as editor in ~/.pryrc.
  • ✅ Starship for bash.
  • ✅ Move STM32_PRG_PATH out of dot_bashrc and dot_zshrc.
  • ✅ Load hombrew completions for zsh in ~/.zsh_local.
  • Templatize chezmoi.toml into .chezmoi.toml.tmpl.
  • Use Windows/link_powershell_configs.ps1 code during run_once script time to link PowerShell 5 and 7.5 configs to same file.
  • work_google_cloud.sh is zsh specific. Create one for bash.
  • ~/.zprofile is empty. Do I need it for anything?
  • Add rc files for POSIX sh, ash, and dash? Fat chance of that happening, it's goofy af.

Add to chezmoi source directory

  • ~/.config/homebrew

  • ~/.config/karabiner/assets/complex_modifications/mbp_only_complex_mods.json

    • vendor_id and product_id will need to be templated and set to mac model specific values.
      • Example for 2021 16" MacBook Pro M1 Max:

        "vendor_id": 1452 "product_id":835

  • ~/.hammerspoon

  • ~/.config/ghostty

  • ~/.config/btop/btop.conf

  • ~/Library/Application Support/REAPER/*

Dependency Management

These dependencies are expected to be installed on the system.

  • chezmoi
  • homebrew
  • git
  • figlet
    • miniwi font for figlet
  • eza
  • bc

chezmoi Gotchas

No landing page for the User Guide docs

The Reference docs land you on page that sets up all the sections of the Reference.

However, the User Guide drops you straight into its first section, Command overview.


Converting a dotfile to a template after it's already been added to the source directory breaks its history in Git.

NOTE: This only applies if you've previously added the config file to the source directory, and it has multiple committed changes.

Using chezmoi chattr +template ~/.foobar breaks the history of the original file by deleting it and adding the templated version as a new file to Git. Here's what the git status looks like:

git status
On branch chezmoi
Your branch is up to date with 'origin/chezmoi'.

Changes not staged for commit:
  ...
        deleted:    dot_bashrc

Untracked files:
  ...
        dot_bashrc.tmpl

To preserve the file's history, use git mv instead.

git mv dot_bashrc dot_bashrc.tmpl
git status
On branch chezmoi
Your branch is up to date with 'origin/chezmoi'.

Changes to be committed:
  ...
        renamed:    dot_bashrc -> dot_bashrc.tmpl

Make file paths relative to the source repo when passing them as arguments to chezmoi commands.

Simply specifying the file name to edit only works if you're currently in $HOME. Otherwise, make sure you specify $HOME or ~ in the file path parameter.

This only works in $HOME:

chezmoi edit .bashrc

Otherwise you'll get this error:

chezmoi: .bashrc: not managed

This will work from any directory:

chezmoi edit ~/.bashrc

See what happened here. TODO: Recreate this link. It was deleted while cleaning up intra-doc links for GitHubs crappy extended Markdown link support.


Be sure to check the properties of files and directories you add manually.

chezmoi will examine your pre-existing config file's permissions when you use chezmoi add to add it to the source directory. The config file's name will be prefaced with some combination of chezmoi's file name prefixes: private_, executable_, and readonly_.

If you add your config file's manually, and you want to maintain the same file permissions, you'll need to add the appropriate chezmoi file permission prefixes to the file's name.

This is the same as using the dot_ prefix for any config file whose name starts with a dot, e.g. .bashrc gets renamed to dot_bashrc in the source directory.


chezmoi file extensions can break Vim's file type detection.

The way chezmoi renames files in the source repo can break Vim's file type detection. For example, Vim will detect .bashrc as a Bash file, but it fails with dot_bashrc.

It happens with the edit command chezmoi edit ~/.bashrc if you've made the source file a template with the .tmpl extension. Here's what I got from :ls in Vim while editing .bashrc:

/private/var/folders/s9/84x1rz2j02g5pth66xsm7vv80000gp/T/chezmoi-edit663884950/.bashrc.tmpl

The quick fix is to run the following in Vim:

:setf bash

Don't use comments in .chezmoiignore.

...or chezmoi.toml


Paths to includes and templates must be relative to the top level template when nesting templates, I think.

The grisly details are in the Appendix section below. TODO: Link to "Getting Templates to Work" section in Appendix below.


Favor the includeTemplate function over the template function.

From chezmoi's intro to templating doc:

chezmoi uses the text/template syntax from Go extended with text template functions from sprig.

template is an existing action in Golang's text/template standard lib. includeTemplate is a function provided by chezmoi. includeTemplate has better context of the source state than template does. This becomes very clear the first time you need to include a template in another themplate.


What template file does this bit of code live in?

It's tricky to figure out what template file your config code lives in when looking at .bashrc or .zshrc. To make it a little easier, I generated section headers and footers using figlet with the miniwi font.

Figlet is easy to install with homebrew.

brew install figlet

Next, download the miniwi font and drop it in the figlet font directory found here:

ls $(brew --prefix figlet)/share/figlet/fonts

I created a shell function to generate the headers and footers and stuck it in my .bashrc and .zshrc.

# Convenience function for generating rc file
# section headers and footers using figlet.
fighead () {
  local header_text="$(echo "$1" | tr '[:lower:]' '[:upper:]')"

  echo -e "BEGIN ${header_text}\nEND ${header_text}" | \
  figlet -w 200 -f miniwi | \

  # Turn each line of header/footer into a shell comment line
  while read -r ln; do echo "# ${ln}"; done
}

Getting stuck in a sub-shell with chezmoi cd

chezmoi cd creates a sub-shell for reasons specified in the docs here.

chezmoi cd spawns a shell because it is not possible for a program to change the working directory of its parent process.

Use this alias in your shell instead:

alias czcd=cd "$(chezmoi source-path)"

Appendix

Set up local repo for the wiki

chezmoi cd
cd ..
git clone git@ghblit:blitterated/dotfiles.wiki.git chezmoi_wiki
cd chezmoi_wiki
git config user.name "blitterated"
git config user.email "[email protected]"

Source repo file system tree before and after results

Before reinit:

tree -a -I .git
drwxr-xr-x@    - blitterated 12 Jul 23:29 .
.rw-r--r--@  539 blitterated 12 Jul 14:01 ├── .chezmoiignore
.rw-r--r--@  10k blitterated  8 Jul 10:42 ├── .DS_Store
.rw-r--r--@   19 blitterated 18 May 22:07 ├── .gitignore
drwxr-xr-x@    - blitterated  2 Jul 14:02 ├── __lib
.rw-r--r--@ 1.1k blitterated  2 Jul 14:02 │   └── source_files.sh
.rw-r--r--@  154 blitterated 12 Jul 01:08 ├── chezmoi.toml
.rw-r--r--@ 1.6k blitterated  9 Jun 18:22 ├── dot_bash_local
.rw-r--r--@   76 blitterated 18 May 22:07 ├── dot_bash_profile
.rw-r--r--@ 2.6k blitterated 12 Jul 23:28 ├── dot_bashrc.tmpl
.rw-r--r--@  114 blitterated 18 May 03:05 ├── dot_bcrc
drwxr-xr-x@    - blitterated  7 Jul 22:32 ├── dot_config
drwxr-xr-x@    - blitterated  6 Jun 00:01 │   ├── nvim
.rw-r--r--@  665 blitterated  6 Jun 00:01 │   │   ├── init.lua
.rw-r--r--@ 1.2k blitterated  7 Jun 15:27 │   │   ├── lazy-lock.json
drwxr-xr-x@    - blitterated  5 Jun 23:59 │   │   └── lua
drwxr-xr-x@    - blitterated  5 Jun 23:59 │   │       ├── config
.rw-r--r--@ 1.3k blitterated  3 Jun 16:52 │   │       │   └── lazy.lua
.rw-r--r--@  162 blitterated  5 Jun 23:48 │   │       ├── linenumber-colors.lua
drwxr-xr-x@    - blitterated  2 Jul 01:19 │   │       ├── plugins
.rw-r--r--@  155 blitterated 20 Apr 18:15 │   │       │   ├── catppuccin.lua
.rw-r--r--@  605 blitterated  7 Jun 15:27 │   │       │   ├── lsp-config.lua
.rw-r--r--@  162 blitterated  5 Jun 23:03 │   │       │   ├── lualine.lua
.rw-r--r--@  413 blitterated  4 Jun 13:42 │   │       │   ├── neotree.lua
.rw-r--r--@  303 blitterated 20 Apr 22:02 │   │       │   ├── telescope.lua
.rw-r--r--@  591 blitterated 20 Apr 21:53 │   │       │   └── treesitter.lua
.rw-r--r--@  540 blitterated  3 Jun 17:40 │   │       └── vim-options.lua
.rw-r--r--@ 3.8k blitterated 20 May 23:40 │   └── starship.toml
.rw-r--r--@   51 blitterated 13 Feb  2024 ├── dot_gemrc
.rw-r--r--@  115 blitterated 25 Mar  2024 ├── dot_gitconfig
.rw-r--r--@   24 blitterated 18 May 03:05 ├── dot_gitignore_global
.rw-r--r--@   66 blitterated 18 May 22:49 ├── dot_pryrc
.rw-r--r--@  987 blitterated 18 May 03:05 ├── dot_psqlrc
.rw-r--r--@ 1.8k blitterated 18 May 03:05 ├── dot_tmux.conf
.rw-r--r--@ 2.5k blitterated 18 May 03:05 ├── dot_vimrc
.rw-r--r--@    0 blitterated 30 May 15:33 ├── dot_zprofile
.rw-r--r--@ 2.1k blitterated  9 Jun 18:27 ├── dot_zsh_local
.rw-r--r--@ 2.2k blitterated  2 Jul 01:34 ├── dot_zshrc
.rw-r--r--@  102 blitterated 18 May 22:07 ├── README.md
.rw-r--r--@  42k blitterated 12 Jul 23:31 ├── 'Switch to chezmoi.md'
drwxr-xr-x@    - blitterated 30 May 15:34 └── Windows
.rw-r--r--@ 1.4k blitterated 30 May 15:34     ├── link_powershell_configs.ps1
drwxr-xr-x@    - blitterated 30 May 15:34     └── PowerShell
.rw-r--r--@ 5.8k blitterated 30 May 15:34         └── Microsoft.PowerShell_profile.ps1

After reinit:

tree -a -I .git
drwxr-xr-x@    - blitterated 13 Jul 00:00 .
.rw-r--r--@  539 blitterated 13 Jul 00:00 ├── .chezmoiignore
.rw-r--r--@   19 blitterated 13 Jul 00:00 ├── .gitignore
drwxr-xr-x@    - blitterated 13 Jul 00:00 ├── __lib
.rw-r--r--@ 1.1k blitterated 13 Jul 00:00 │   └── source_files.sh
.rw-r--r--@  154 blitterated 13 Jul 00:00 ├── chezmoi.toml
.rw-r--r--@ 1.6k blitterated 13 Jul 00:00 ├── dot_bash_local
.rw-r--r--@   76 blitterated 13 Jul 00:00 ├── dot_bash_profile
.rw-r--r--@ 2.6k blitterated 13 Jul 00:00 ├── dot_bashrc.tmpl
.rw-r--r--@  114 blitterated 13 Jul 00:00 ├── dot_bcrc
drwxr-xr-x@    - blitterated 13 Jul 00:00 ├── dot_config
drwxr-xr-x@    - blitterated 13 Jul 00:00 │   ├── nvim
.rw-r--r--@  665 blitterated 13 Jul 00:00 │   │   ├── init.lua
.rw-r--r--@ 1.2k blitterated 13 Jul 00:00 │   │   ├── lazy-lock.json
drwxr-xr-x@    - blitterated 13 Jul 00:00 │   │   └── lua
drwxr-xr-x@    - blitterated 13 Jul 00:00 │   │       ├── config
.rw-r--r--@ 1.3k blitterated 13 Jul 00:00 │   │       │   └── lazy.lua
.rw-r--r--@  162 blitterated 13 Jul 00:00 │   │       ├── linenumber-colors.lua
drwxr-xr-x@    - blitterated 13 Jul 00:00 │   │       ├── plugins
.rw-r--r--@  155 blitterated 13 Jul 00:00 │   │       │   ├── catppuccin.lua
.rw-r--r--@  605 blitterated 13 Jul 00:00 │   │       │   ├── lsp-config.lua
.rw-r--r--@  162 blitterated 13 Jul 00:00 │   │       │   ├── lualine.lua
.rw-r--r--@  413 blitterated 13 Jul 00:00 │   │       │   ├── neotree.lua
.rw-r--r--@  303 blitterated 13 Jul 00:00 │   │       │   ├── telescope.lua
.rw-r--r--@  591 blitterated 13 Jul 00:00 │   │       │   └── treesitter.lua
.rw-r--r--@  540 blitterated 13 Jul 00:00 │   │       └── vim-options.lua
.rw-r--r--@ 3.8k blitterated 13 Jul 00:00 │   └── starship.toml
.rw-r--r--@   51 blitterated 13 Jul 00:00 ├── dot_gemrc
.rw-r--r--@  115 blitterated 13 Jul 00:00 ├── dot_gitconfig
.rw-r--r--@   24 blitterated 13 Jul 00:00 ├── dot_gitignore_global
.rw-r--r--@   66 blitterated 13 Jul 00:00 ├── dot_pryrc
.rw-r--r--@  987 blitterated 13 Jul 00:00 ├── dot_psqlrc
.rw-r--r--@ 1.8k blitterated 13 Jul 00:00 ├── dot_tmux.conf
.rw-r--r--@ 2.5k blitterated 13 Jul 00:00 ├── dot_vimrc
.rw-r--r--@    0 blitterated 13 Jul 00:00 ├── dot_zprofile
.rw-r--r--@ 2.1k blitterated 13 Jul 00:00 ├── dot_zsh_local
.rw-r--r--@ 2.2k blitterated 13 Jul 00:00 ├── dot_zshrc
.rw-r--r--@  102 blitterated 13 Jul 00:00 ├── README.md
.rw-r--r--@  42k blitterated 13 Jul 00:00 ├── 'Switch to chezmoi.md'
drwxr-xr-x@    - blitterated 13 Jul 00:00 └── Windows
.rw-r--r--@ 1.4k blitterated 13 Jul 00:00     ├── link_powershell_configs.ps1
drwxr-xr-x@    - blitterated 13 Jul 00:00     └── PowerShell
.rw-r--r--@ 5.8k blitterated 13 Jul 00:00         └── Microsoft.PowerShell_profile.ps1

They're the same except for .DS_Store.


Getting Templates to Work

I'd extracted common shell config code to various template files. Code common to both .bashrc and .zshrc on all machines was extracted to shell_common/bash_zsh_common.sh. Mac specific shell config in .bashrc was extracted to shell_local/bash_darwin.sh.tmpl. Mac specific shell config in .zshrc was extracted to shell_local/zsh_darwin.sh.tmpl.

mkdir shell_common
mv bash_zsh_common.sh shell_common
nvim .chezmoiignore
chezmoi edit ~/.bashrc ~/.zshrc
nvim shell_common/bash_zsh_common.sh
nvim shell_local/*
nvim shell_common/bash_zsh_common.sh
nvim shell_common/bash_zsh_common.sh
nvim shell_common/bash_zsh_common.sh shell_local/bash_darwin shell_local/zsh_darwin
nvim shell_local/bash_darwin shell_local/zsh_darwin shell_local/bash_zsh_common_darwin.sh
cd shell_local
git mv bash_darwin bash_darwin.sh.tmpl
git mv zsh_darwin zsh_darwin.sh.tmpl
nvim bash_darwin.sh.tmpl zsh_darwin.sh.tmpl ../dot_zshrc.tmpl
chezmoi apply
chezmoi: .bashrc: template: dot_bashrc.tmpl:13:5:
executing "dot_bashrc.tmpl" at <include "shell_local/zsh_darwin">:
error calling include: open /Users/blitterated/.local/share/chezmoi/shell_local/zsh_darwin: no such file or directory

Whoops, forgot to add .sh.tmpl to include "shell_local/zsh_darwin" in dot_zshrc.tmpl.

nvim dot_zshrc.tmpl dot_bashrc.tmpl
chezmoi apply
diff --git a/.bashrc b/.bashrc
index 09d966e75d13d6cb7652c22b09fd3c826f5e3f41..3e65225f42730fe164bf8b3ca12b499b03f9a6c2 100644
...
diff --git a/.zshrc b/.zshrc
index c70b2328b096dcaeb5c56cbf4dc54b973017c17b..3ea5bf71e70671ca71a5061fdad187c1807e8de8 100644

It rendered correctly, but include just copies file contents. I got the raw contents of the template files with the template code instead of the executed template results. I changed from calling include to calling template.

nvim dot_zshrc.tmpl dot_bashrc.tmpl
chezmoi apply
chezmoi: .bashrc: template: dot_bashrc.tmpl:18:14:
executing "dot_bashrc.tmpl" at <{{template "shell_local/bash_darwin.sh.tmpl"}}>:
template "shell_local/bash_darwin.sh.tmpl" not defined

I checked that the template files were in the right location, and they were. So I applied the changes again.

nvim dot_zshrc.tmpl dot_bashrc.tmpl
chezmoi apply
chezmoi: .bashrc: template: dot_bashrc.tmpl:18:14:
executing "dot_bashrc.tmpl" at <{{template "./bash_darwin.sh.tmpl"}}>:
template "./bash_darwin.sh.tmpl" not defined

Same issue. I followed some documentation and moved the template files to the .chezmoitemplates directory in the root of the dotfiles repo directory. The tree results show that the template files were moved out of the directory.

cd shell_local
mkdir .chezmoitemplates
mv *.sh.tmpl .chezmoitemplates
tree
.
├── bash_zsh_common_darwin.sh
├── README.md
└── work_google_cloud.sh

Here's the tree listing for the .chezmoitemplates directory.

t -a
.
├── .chezmoitemplates
│   ├── bash_darwin.sh.tmpl
│   └── zsh_darwin.sh.tmpl
├── bash_zsh_common_darwin.sh
├── README.md
└── work_google_cloud.sh

Like a dumbass, I used mv to move files instead of git mv. I fixed that up to retain git history, and applied the changes again.

nvim ../shell_common/bash_zsh_common.sh
mv .chezmoitemplates/*.sh.tmpl .
git mv *.sh.tmpl .chezmoitemplates
cd .chezmoitemplates
nvim *
cd ..
cd ..
nvim dot_bashrc.tmpl dot_zshrc.tmpl
chezmoi apply
chezmoi: .bashrc: template: dot_bashrc.tmpl:18:14:
executing "dot_bashrc.tmpl" at <{{template "bash_darwin.sh.tmpl"}}>:
template "bash_darwin.sh.tmpl" not defined

I wound up moving the templates into a shell_local directory in chezmoitemplates.

nvim dot_bashrc.tmpl dot_zshrc.tmpl
chezmoi apply
chezmoi: .bashrc: template: dot_bashrc.tmpl:18:14:
executing "dot_bashrc.tmpl" at <{{template "shell_local/bash_darwin.sh.tmpl"}}>:
template "shell_local/bash_darwin.sh.tmpl" not defined

Some fiddling with file paths and a retry.

nvim dot_bashrc.tmpl dot_zshrc.tmpl
chezmoi apply
chezmoi: .bashrc: template: dot_bashrc.tmpl:18:14:
executing "dot_bashrc.tmpl" at <{{template "./shell_local/bash_darwin.sh.tmpl"}}>:
template "./shell_local/bash_darwin.sh.tmpl" not defined

Applying with -v didn't give any extra information.

chezmoi apply -v
chezmoi: .bashrc: template: dot_bashrc.tmpl:18:14:
executing "dot_bashrc.tmpl" at <{{template "./shell_local/bash_darwin.sh.tmpl"}}>:
template "./shell_local/bash_darwin.sh.tmpl" not defined

I found out that template comes with Go's text/template library, but chezmoi has its own call called includeTemplate so I gave that a try. This comment on a chezmoi GitHub issue pointed me in that direction.

nvim dot_bashrc.tmpl dot_zshrc.tmpl
chezmoi apply -v
chezmoi: .bashrc: template: dot_bashrc.tmpl:18:5:
executing "dot_bashrc.tmpl" at <includeTemplate "./shell_local/bash_darwin.sh.tmpl">:
error calling includeTemplate: open /Users/blitterated/.local/share/chezmoi/shell_local/bash_darwin.sh.tmpl:
no such file or directory

Next, I moved the *.sh.tmpl out of .chezmoitemplates/ back to the root shell_local directory.

cd shell_local
mv .chezmoitemplates/*.sh.tmpl .
chezmoi apply
chezmoi: .bashrc: template: dot_bashrc.tmpl:18:5:
executing "dot_bashrc.tmpl" at <includeTemplate "./shell_local/bash_darwin.sh.tmpl">:
error calling includeTemplate: ./shell_local/bash_darwin.sh.tmpl:9:3:
executing "./shell_local/bash_darwin.sh.tmpl" at <include "./bash_zsh_common_darwin.sh">:
error calling include: open /Users/blitterated/.local/share/chezmoi/bash_zsh_common_darwin.sh:
no such file or directory

I patched up file paths I missed in the templates, and it finally worked!

nvim *.sh.tmpl
chezmoi apply -v
diff --git a/.bashrc b/.bashrc
index 3e65225f42730fe164bf8b3ca12b499b03f9a6c2..c2a3c347119bf65f1203baae17eb118bc24e1130 100644
--- a/.bashrc
+++ b/.bashrc
@@ -32,8 +32,8 @@
...
diff --git a/.zshrc b/.zshrc
index 3ea5bf71e70671ca71a5061fdad187c1807e8de8..f5072e69417a640f1d5824220e635a06a1d8caaf 100644
--- a/.zshrc
+++ b/.zshrc
@@ -32,8 +32,8 @@
...

Files in Source Directory Prior to Going Multi-Machine

Config
chezmoi.toml
.chezmoiignore
Templates
dot_bashrc.tmpl
dot_zshrc.tmpl
machine_local/macOS/bashrc.tmpl
machine_local/macOS/zshrc.tmpl
Dot Files
dot_bash_profile
dot_bcrc
dot_gemrc
dot_gitconfig
dot_gitignore_global
dot_pryrc
dot_psqlrc
dot_tmux.conf
dot_vimrc
dot_zprofile
dot_config/nvim/**
dot_config/starship.toml
machine_local/macOS/bash_zsh_common.sh
machine_local/macOS/STM32CubeProg.sh
machine_local/macOS/work_google_cloud_zsh.sh
shell_common/bash_zsh_common.sh
Windows/PowerShell/Microsoft.PowerShell_profile.ps1
Worry About These Later Description
README.md dotfile repo README.
__lib/source_files.sh A shell script that sources all scripts
in a given directory when sourced itself.
machine_local/README.md Description of what files go in the machine_local directory.
Windows/link_powershell_configs.ps1 Windows configs. Currently incorrectly pathed for chezmoi.

Find the MacBook Pro 2021 M1 Keyboard's Identifiers

Get a list of system_profiler's available DataTypes.

system_profiler -listDataTypes
Available Datatypes:
SPParallelATADataType
SPUniversalAccessDataType
SPSecureElementDataType
SPApplicationsDataType
SPAudioDataType
SPBluetoothDataType
SPCameraDataType
SPCardReaderDataType
SPiBridgeDataType
SPDeveloperToolsDataType
SPDiagnosticsDataType
SPDisabledSoftwareDataType
SPDiscBurningDataType
SPEthernetDataType
SPExtensionsDataType
SPFibreChannelDataType
SPFireWireDataType
SPFirewallDataType
SPFontsDataType
SPFrameworksDataType
SPDisplaysDataType
SPHardwareDataType
SPInstallHistoryDataType
SPInternationalDataType
SPLegacySoftwareDataType
SPNetworkLocationDataType
SPLogsDataType
SPManagedClientDataType
SPMemoryDataType
SPNVMeDataType
SPNetworkDataType
SPPCIDataType
SPParallelSCSIDataType
SPPowerDataType
SPPrefPaneDataType
SPPrintersSoftwareDataType
SPPrintersDataType
SPConfigurationProfileDataType
SPRawCameraDataType
SPSASDataType
SPSerialATADataType
SPSPIDataType
SPSmartCardsDataType
SPSoftwareDataType
SPStartupItemDataType
SPStorageDataType
SPSyncServicesDataType
SPThunderboltDataType
SPUSBDataType
SPNetworkVolumeDataType
SPAirPortDataType

Dump the output of all available DataTypes into one file.

system_profiler SPParallelATADataType SPUniversalAccessDataType SPSecureElementDataType SPApplicationsDataType SPAudioDataType SPBluetoothDataType SPCameraDataType SPCardReaderDataType SPiBridgeDataType SPDeveloperToolsDataType SPDiagnosticsDataType SPDisabledSoftwareDataType SPDiscBurningDataType SPEthernetDataType SPExtensionsDataType SPFibreChannelDataType SPFireWireDataType SPFirewallDataType SPFontsDataType SPFrameworksDataType SPDisplaysDataType SPHardwareDataType SPInstallHistoryDataType SPInternationalDataType SPLegacySoftwareDataType SPNetworkLocationDataType SPLogsDataType SPManagedClientDataType SPMemoryDataType SPNVMeDataType SPNetworkDataType SPPCIDataType SPParallelSCSIDataType SPPowerDataType SPPrefPaneDataType SPPrintersSoftwareDataType SPPrintersDataType SPConfigurationProfileDataType SPRawCameraDataType SPSASDataType SPSerialATADataType SPSPIDataType SPSmartCardsDataType SPSoftwareDataType SPStartupItemDataType SPStorageDataType SPSyncServicesDataType SPThunderboltDataType SPUSBDataType SPNetworkVolumeDataType SPAirPortDataType | tee ~/system_profiler_all_datatypes.txt

See if there are any hits.

ggrep -P "0x5AC" system_profiler_all_datatypes.txt 
ggrep -P "0x343" system_profiler_all_datatypes.txt 

There are, but it's difficult to determine for which DataTypes.

Write the output of each DataType into its own file.

sp_out=( $(system_profiler -listDataTypes) )
sp_datatypes=("${sp_out[@]:2}")

idx=0
for dt in "${sp_datatypes[@]}"; do
  echo "${idx}: ${dt}"
  system_profiler $dt | tee "${dt}.txt"
  ((idx=idx+1))
done

Use rg to find matches.

rg -i "0x5AC" *.txt
rg -i "0x343" *.txt

They're in SPLogsDataType.txt. That's not very useful. It's a dump of a dump of something else.

May need to live with manually updating mbp_only_complex_mods.json for Karabiner.


Rabbit hole where I tried to recreate mkdir -p

Quick experiment with splitting and iterating a path in shell script.

This was a waste of time, because I'd completely blocked out mkdir -p from my mind.

Define the path string

karabiner_complex_config_path="~/.config/karabiner/assets/complex_modifications"

Splitting the path in bash and zsh

Path splitting in bash:

IFS='/' read -ra cfg_paths <<< "${karabiner_complex_config_path}"

Path splitting in zsh:

cfg_paths=( ${(@s:/:)karabiner_complex_config_path} )

Iterating the array of path names

idx=0
dir_path=""
for dir in "${cfg_paths[@]}"; do
  ((idx=idx+1))
  $dir_path="${dir_path}/${dir}"
  $local:dir_exists=[ -d "${dir_path}" ] && "already exists" || "is creatable"
  echo "${idx}: ${dir_path} ${dir_exists}"
done

Oops!

...and then I remembered mkdir -p

~fin.


File permission prefixes

Drop into the source directory

cd "$(chezmoi source-path)"

Generate test files

Run the following in irb or pry to see what permissions chezmoi apply generates.

def make_chezmoi_file_permission_test_files()
  (1..3).sum([]) do |n|
    #["executable", "private", "readonly"]
    #["private", "readonly", "executable"]
    ["private", "readonly", "executable", "dot"]
    .combination(n)
    .to_a
  end.each.with_index do |ary, idx|
    #test_fname = "dot_#{ary.join("_")}_test#{"%02i" % idx}"
    #test_fname = "#{ary.join("_")}_dot_test#{"%02i" % idx}"
    test_fname = "#{ary.join("_")}_test#{"%02i" % idx}"
    #test_fname = "#{ary.reverse.join("_")}_test#{"%02i" % idx}"
    File.open(test_fname, "w") { |f| f.write(test_fname) }
  end

  puts "Test files written to current directory: \"#{Dir.pwd}\"\n\n"
end

make_chezmoi_file_permission_test_files

Check generated files' permissions

chezmoi apply
ls -l .*_test??
.rw-r--r--@ 38 blitterated 23 Oct 01:10 .executable_private_readonly_test06
.rw-r--r--@ 29 blitterated 23 Oct 01:10 .executable_private_test03
.rw-r--r--@ 30 blitterated 23 Oct 01:10 .executable_readonly_test04
.rw-r--r--@ 21 blitterated 23 Oct 01:10 .executable_test00
.rw-r--r--@ 27 blitterated 23 Oct 01:10 .private_readonly_test05
.rw-r--r--@ 18 blitterated 23 Oct 01:10 .private_test01
.rw-r--r--@ 19 blitterated 23 Oct 01:10 .readonly_test02

WTF?!?!

Maybe it's because the dot_ prefix needs to be last, not first.

Altered make_chezmoi_file_permission_test_files() to move it to the last prefix spot, and reran the test.

    test_fname = "#{ary.join("_")}_dot_test#{"%02i" % idx}"
.rwxr-xr-x@   21 peteyoung 23 Oct 14:24 .test00
.rw-------@   18 peteyoung 23 Oct 14:24 .test01
.r--r--r--@   19 peteyoung 23 Oct 14:24 .test02
.r--------@   27 peteyoung 23 Oct 14:24 .test05
.rwxr-xr-x@   29 peteyoung 23 Oct 14:24 private_dot_test03
.rwxr-xr-x@   38 peteyoung 23 Oct 14:24 private_readonly_dot_test06
.rwxr-xr-x@   30 peteyoung 23 Oct 14:24 readonly_dot_test04

03, 04, and 06 are not being processed correctly.

Tried it without the dot_ prefix beingin the mix.

    test_fname = "#{ary.join("_")}_test#{"%02i" % idx}"
.rwxr-xr-x@ 34 peteyoung 23 Oct 17:46 private_readonly_test06
.rwxr-xr-x@ 25 peteyoung 23 Oct 17:46 private_test03
.rwxr-xr-x@ 26 peteyoung 23 Oct 17:46 readonly_test04
.rwxr-xr-x@ 17 peteyoung 23 Oct 17:46 test00
.rw-------@ 14 peteyoung 23 Oct 17:46 test01
.r--r--r--@ 15 peteyoung 23 Oct 17:46 test02
.r--------@ 23 peteyoung 23 Oct 17:46 test05

03, 04, and 06 are still not being processed correctly.

Maybe it's the order of the prefixes? I tried reversing the list of prefixes to see if changing prefix' order made a difference.

    test_fname = "#{ary.reverse.join("_")}_test#{"%02i" % idx}"
.r--r--r--@ 34 peteyoung 23 Oct 18:05 private_executable_test06
.r--r--r--@ 23 peteyoung 23 Oct 18:05 private_test05
.rwxr-xr-x@ 17 peteyoung 23 Oct 18:05 test00
.rw-------@ 14 peteyoung 23 Oct 18:05 test01
.r--r--r--@ 15 peteyoung 23 Oct 18:05 test02
.rwx------@ 25 peteyoung 23 Oct 18:05 test03
.r-xr-xr-x@ 26 peteyoung 23 Oct 18:05 test04

Interestingly, we got 03 and 04, but lost 05. Let's compare the original source files with the last batch.

Generated Source Files Generated Configs Reverse Generated Configs Reverse Generated Source Files
executable_test00 test00 test00 executable_test00
private_test01 test01 test01 private_test01
readonly_test02 test02 test02 readonly_test02
executable_private_test03 private_test03 test03 private_executable_test03
executable_readonly_test04 readonly_test04 test04 readonly_executable_test04
private_readonly_test05 test05 private_test05 readonly_private_test05
executable_private_readonly_test06 private_readonly_test06 private_executable_test06 readonly_private_executable_test06

Looking at 03 and 04, it appears private_ and readonly need to go before executable_. And looking at 05, it appears that private_ needs to go before readonly. And don't forget, all the above need to go before dot_.

Let's try what we've discovered with the source attributes.

    ["private", "readonly", "executable"]
...
    test_fname = "#{ary.join("_")}_test#{"%02i" % idx}"
.rw-------@ 14 peteyoung 24 Oct 00:29 test00
.r--r--r--@ 15 peteyoung 24 Oct 00:29 test01
.rwxr-xr-x@ 17 peteyoung 24 Oct 00:29 test02
.r--------@ 23 peteyoung 24 Oct 00:29 test03
.rwx------@ 25 peteyoung 24 Oct 00:29 test04
.r-xr-xr-x@ 26 peteyoung 24 Oct 00:29 test05
.r-x------@ 34 peteyoung 24 Oct 00:29 test06

That looks good! Let's check the contents of the files.

for i in {0..6}; do echo "test0${i}: $(cat test0${i})"; done
test00: private_test00
test01: readonly_test01
test02: executable_test02
test03: private_readonly_test03
test04: private_executable_test04
test05: readonly_executable_test05
test06: private_readonly_executable_test06

Hell Yeah! Now for one last test that adds the dot_ prefix back into the mix

    ["private", "readonly", "executable", "dot"]

Then list and somewhat sort the files.

ls -1 (.|)test?? | sort
.test03
.test06
.test08
.test09
.test11
.test12
.test13
test00
test01
test02
test04
test05
test07
test10

That looks promising. Let's try using ruby with a regex to list and sort the files.

Here's a monster one-liner for the shell.

ruby -e 'Dir.glob("{*,.*}").select { |file| file =~ /\.?test\d{2}/ }.sort_by { |fn| fn.match(/\.?test(\d{2})/)[1].to_i }.each { |fn| puts "#{File.stat(fn).mode.to_s(8)[-3..-1].ljust(6)}#{fn.ljust(10)}#{File.read(fn)}" }'

- or -

Run this in pry.

rgx = /\.?test(\d{2})/
Dir.glob("{*,.*}")
   .select  { |fn| fn =~ rgx }
   .sort_by { |fn| fn.match(rgx)[1].to_i }
   .each do |fn|
      mode = File.stat(fn).mode.to_s(8)[-3..-1]
      srcf = File.read(fn)
      puts "#{mode.ljust(6)}#{fn.ljust(10)}#{srcf}"
   end
nil
600   test00    private_test00
444   test01    readonly_test01
755   test02    executable_test02
644   .test03   dot_test03
400   test04    private_readonly_test04
700   test05    private_executable_test05
600   .test06   private_dot_test06
555   test07    readonly_executable_test07
444   .test08   readonly_dot_test08
755   .test09   executable_dot_test09
500   test10    private_readonly_executable_test10
400   .test11   private_readonly_dot_test11
700   .test12   private_executable_dot_test12
555   .test13   readonly_executable_dot_test13

HELL YEAH!!!

Delete test config files chezmoi wrote to $HOME

rm ~/.*_test??

Delete generated files from the source directory

cd "$(chezmoi source-path)"
rm ./dot_*_test??

File Permissions Table

chezmoi Source Attribute $HOME
Private Readonly Executable Long Mode Mode File Name Source File Name
X     rwxr-xr-x 755 .test00 executable_dot_test00
  X   rw------- 600 .test01 private_dot_test01
    X r--r--r-- 444 .test02 readonly_dot_test02
X X   rwxr-xr-x
(rwx------)
755
(700)
private_dot_test03
(.test03)
executable_private_dot_test03
X   X rwxr-xr-x
(r-xr-xr-x)
755
(555)
readonly_dot_test04
(.test04)
executable_readonly_dot_test04
  X X r-------- 400 .test05 private_readonly_dot_test05
X X X rwxr-xr-x
(r-x------)
755
(500)
private_readonly_dot_test06
(.test06)
executable_private_readonly_dot_test06
chezmoi Source Attribute $HOME
Private Readonly Executable Dotfile Long Mode Mode File Name Source File Name
X       rw------- 600 test00 private_test00
  X     r--r--r-- 444 test01 readonly_test01
    X   rwxr-xr-x 755 test02 executable_test02
      X rw-r--r-- 644 .test03 dot_test03
X X     r-------- 400 test04 private_readonly_test04
X   X   rwx------ 700 test05 private_executable_test05
X     X rw------- 600 .test06 private_dot_test06
  X X   r-xr-xr-x 555 test07 readonly_executable_test07
  X   X r--r--r-- 444 .test08 readonly_dot_test08
    X X rwxr-xr-x 755 .test09 executable_dot_test09
X X X   r-x------ 500 test10 private_readonly_executable_test10
X X   X r-------- 400 .test11 private_readonly_dot_test11
X   X X rwx------ 700 .test12 private_executable_dot_test12
  X X X r-xr-xr-x 555 .test13 readonly_executable_dot_test13

Here's the Ruby script for generating table rows above from source directory filenames.

SourceAttrs = [ "private_", "readonly_", "executable_", "dot_" ].freeze

LongForms = { 0 => "---", 4 => "r--", 5 => "r-x", 6 => "rw-", 7 => "rwx" }.freeze

def filter_and_sort(files)
  rgx = /\.?test(\d{2})/
  files.select { |fn| fn =~ rgx }.sort_by { |fn| fn.match(rgx)[1].to_i }
end

def make_chezmoi_source_attribute_table_rows(files)

  filter_and_sort(files).each do |fname|
    src_fname = File.read(fname) # test file's contents are its source file name.
    mode = File.stat(fname).mode.to_s(8)[-3..-1]
    perms = mode.chars.map { |n| LongForms[n.to_i] }.join

    sa_cells = SourceAttrs.map do |sa_prefix|
      if src_fname.include?(sa_prefix)
        "<td><strong>X</strong></td>" else
        "<td>&nbsp;</td>" end
    end

    ind = ' ' * 2

    tbl_cells = (sa_cells + [
      "<td><code>#{perms}</code></td>",
      "<td>#{mode}</td>",
      "<td><code>#{fname}</code></td>",
      "<td><code>#{src_fname}</code></td>"
    ]).join("\n#{ind*3}")

    puts [
      "#{ind*2}<tr>",
      "#{ind}#{tbl_cells}",
      "</tr>"
    ].join("\n#{ind*2}")
  end
end

make_chezmoi_source_attribute_table_rows Dir.glob("{*,.*}")

Resources

⚠️ **GitHub.com Fallback** ⚠️