Personal dotfiles managed as a bare git repository with $HOME as the working tree.
Two repos:
~/.dotfiles— bare repo, personal dotfiles (public)~/.dotfiles-work— regular clone, work dotfiles (private, optional)
Work files are symlinked into $HOME by the work repo's bootstrap script. Personal dotfiles use [ -f ] guards to source work files only when present.
macOS note: Requires Bash 4+ (brew install bash). The system Bash (3.2) is too old.
Personal machine:
curl -sL https://raw.githubusercontent.com/cgraf78/dotfiles/main/.local/bin/dotbootstrap | bash -s init
source ~/.bashrcWork machine (also clones work repo):
curl -sL https://raw.githubusercontent.com/cgraf78/dotfiles/main/.local/bin/dotbootstrap | bash -s init work
source ~/.bashrcOn subsequent runs, dotbootstrap with no argument auto-detects work mode if ~/.dotfiles-work exists. The bootstrap script automatically backs up any conflicting files to ~/.dotfiles-backup/<timestamp>/.
After bootstrap, dot update self-installs a cron that keeps the machine updated automatically (see Auto-update cron).
dot update # sync everything: pull repos, merge configs, update deps
dot update --cron # same, but quiet + skip if worktree is dirty (for cron)
dot pull # same as update (requires bare repo)
dot fetch # fetch both repos (without updating working copy)
dot push # push both repos
dot status # check status of both repos
dot diff # diff both repos
dot add <file> # track a file in personal repo
dot commit -m "" # commit to personal repo
dot refresh # fix phantom dirty files from clean/smudge filters
dot cron # show installed cron entriesupdate works on all machines with the bare repo. Installs cron entries from ~/.config/dot/cron into the user crontab. All other commands also require the bare repo.
Work repo files are managed with plain git in ~/.dotfiles-work/.
dot updatesyncs everything: pulls repos (if present), merges configs, updates deps- If a pull updates dot infrastructure (
.config/dot/or.local/bin/dot), the script re-execs itself so the rest of the run uses the new code — no need to rundot updatetwice dot fetch/pull/push/status/diffoperates on both repos (if~/.dotfiles-workexists)- Work bootstrap symlinks files from
~/.dotfiles-work/home/into$HOME - Files that override personal versions get
--skip-worktreeto prevent phantom dirty status - No branch sync, no markers — just two independent repos
dot update installs cron entries from ~/.config/dot/cron into the user crontab. By default this runs dot update --cron every 30 minutes, keeping all machines up to date automatically after the initial dotbootstrap.
The --cron flag enables two safety behaviors:
- Skip if dirty — if either repo has uncommitted changes, the update is skipped entirely. This prevents stomping on in-progress dotfile editing.
- Quiet mode — suppresses all output unless something goes wrong.
Every machine is a peer — no primary/replica roles. All machines pull from git independently.
To change the schedule or add more cron entries, edit ~/.config/dot/cron. The file is a tracked dotfile — changes propagate to all machines on the next dot update. For machine-local entries that shouldn't propagate, use ~/.config/dot/cron.local (same format, untracked). Lines starting with # are comments. $HOME is expanded and PATH is injected automatically at install time.
Run dot cron to see what's currently installed.
.bashrc is the entry point for all shell config. Platform-specific sections (macOS, Linux/WSL/MINGW) are inline, guarded by uname checks. Sourced files:
.bashrc
├── .bashrc_work (work-only — sourced first, symlinked from work repo if present)
├── .bashrc_local (machine-local, not tracked)
└── .bashrc_local_work (machine-local work-only, not tracked)
.bash_aliases contains all aliases, including platform-specific (macOS, Linux/WSL/MINGW), inline with uname guards. Sourced files:
.bash_aliases
└── .bash_aliases_work (work-only — symlinked from work repo if present)
Settings and keybindings in ~/.config/dot/vscode/ are merged into VS Code's config dirs by dot update and dot pull:
~/.config/dot/vscode/
├── settings.json (cross-platform)
├── keybindings.json (common keybindings, all platforms)
├── keybindings-mac.json (macOS-specific)
├── keybindings-windows.json (Windows/WSL-specific)
└── keybindings-linux.json (Linux-specific)
Merge policy: dotfiles win on conflicts, local-only settings/keybindings are preserved, JSONC comments are stripped.
Dynamic Profile in ~/.config/dot/iterm2/dotfiles-dyn-profile.json is copied into iTerm2's DynamicProfiles dir by dot update and dot pull. Set it as default in Preferences.
Profiles in ~/.config/dot/karabiner/karabiner.json are merged into Karabiner's config by dot update and dot pull. Merge policy: dotfiles profiles replace local profiles with the same name, local-only profiles are preserved.
~/.config/wezterm/wezterm.lua is the WezTerm config file. Tracked directly. On WSL, dot update and dot pull copy it to the Windows home so the Windows-native WezTerm picks it up.
Add the file to ~/.dotfiles-work/home/<path>, commit, and push. The work bootstrap will symlink it on the next dot pull.
dot add <file> && dot commit -m "add <file>" && dot pushdot update installs and upgrades tools defined in ~/.config/dot/deps.conf. Each line declares a dependency with a name and install method:
# name method cmd alt overrides repo dir
jq pkg
bat pkg bat batcat
fd pkg fd fdfind apt:fd-find,dnf:fd-find
ds git - - - cgraf78/ds.git .local/share/ds
neovim appimage nvim - - neovim/neovim
fonts custom
Methods:
pkg— system package (brew,apt,dnf,pacman). Batches all packages into one install command.git— clones from GitHub (prefers~/git/<name>local clones, falls back to release tarballs, thengit clone).appimage— downloads GitHub AppImage releases on Linux, falls back topkgon macOS/WSL.custom— entirely managed by a post-install hook. The hook handles platform detection, idempotency, and installation.
Platform overrides: The overrides column maps package managers to platform-specific names (e.g., apt:fd-find). Use NONE to skip a dep on a platform (e.g., apt:NONE).
Post-install hooks: Defined in ~/.config/dot/deps-hooks.sh as _post_<name>() functions (dashes in dep name become underscores). Run after installation when a dep is new or updated.
Existence checks: pkg deps check command -v first, then fall back to querying the package manager directly (brew list, dpkg -s, etc.) — useful for deps like fonts that install no binary.