Babashka-based dotfile manager. Installs packages and symlinks config files via a declarative manifest.
- A folder for each application
- Loose coupling between applications
- Be declarative
- Use the application's native config files
- Split config into multiple files where possible
- Comment liberally
- Use application defaults most of the time
- Create macOS account and sign into Mac App Store (so mas works)
- Clone this repo to
~/dotfiles - Install prerequisites: Homebrew, then
brew install borkdude/brew/babashka git-crypt - Unlock secrets:
git-crypt unlock(requires GPG key) - Run
~/dotfiles/cfg/bin/bin/bootstrap
bootstrap # Run all actions
bootstrap :pkg/brew # Only brew packages
bootstrap :fs/symlink # Only symlinks
bootstrap --dry-run # Preview without executing├── manifest.edn # Main manifest
├── secrets.edn # Encrypted secrets (git-crypt)
├── bb.edn # Babashka config
├── src/ # Installer code
└── cfg/ # Dotfile configs
└── <app>/
├── manifest.edn # Package/symlink definitions
└── .config/ # Actual config files to symlink
manifest.edn has a :plan key containing entries to process:
{:plan [:ghostty ; keyword -> cfg/ghostty/manifest.edn
"cfg/custom/config.edn" ; string -> explicit path
{:pkg/brew {:ripgrep {}}}]} ; map -> inline actions| Action | Description | Options |
|---|---|---|
:pkg/brew |
Homebrew packages | :head, :cask |
:pkg/mise |
mise version manager | :version (required), :global |
:pkg/mas |
Mac App Store | {app-name app-id} |
:pkg/bbin |
Babashka binaries | :url, :local, :as, :git/tag, :git/sha, :main-opts |
:pkg/npm |
npm global packages | - |
:pkg/script |
Run shell scripts | :path or :src |
{:pkg/brew {:ripgrep {}
:neovim {:head true}
:rectangle {:cask true}}
:pkg/mise {:node {:version "22" :global true}}
:pkg/mas {"Tailscale" 1475387142}
:pkg/bbin {:neil {:url "https://github.com/babashka/neil"}}}| Action | Description |
|---|---|
:fs/symlink |
Symlink individual files |
:fs/symlink-folder |
Symlink all files in a folder recursively |
{:fs/symlink {"~/.gitconfig" "./gitconfig"}
:fs/symlink-folder {"~/.config/nvim" "./.config/nvim"}}| Action | Description | Options |
|---|---|---|
:osx/defaults |
macOS defaults | :domain, :settings or :key/:value |
:brew/service |
Homebrew services | :restart, :sudo |
{:osx/defaults {:dock {:domain "com.apple.dock"
:settings {:autohide true
:tilesize 48}}}
:brew/service {:postgresql {:restart true}}}| Action | Description | Options |
|---|---|---|
:claude/marketplace |
Add plugin marketplaces | :source |
:claude/plugin |
Install plugins | - |
:claude/mcp |
Add MCP servers | :command, :args, :env, :scope |
{:claude/marketplace {:my-marketplace {:source "user/repo"}}
:claude/plugin {:some-plugin {}}
:claude/mcp {:my-server {:command "npx"
:args ["-y" "some-mcp-server"]
:env {:API_KEY #secret :my-api-key}}}}Actions can declare dependencies using :dep/provides and :dep/requires:
;; In bootstrap: mise installation provides :pkg/mise capability
{:pkg/brew {:mise {:dep/provides #{:pkg/mise}}}}
;; Later: node installation requires mise to be installed first
{:pkg/mise {:node {:version "22"
:dep/requires #{:pkg/mise}}}}
;; Depend on a specific action (not just capability)
{:claude/mcp {:my-server {:dep/requires #{[:pkg/brew :claude-code]
[:pkg/mise :node]}}}}Actions are executed in topological order based on dependencies.
Sensitive values live in secrets.edn (encrypted with git-crypt):
;; secrets.edn
{:github-token "ghp_xxx"
:api-key "sk-xxx"}Reference secrets in config with the #secret reader tag:
{:claude/mcp {:server {:env {:API_KEY #secret :api-key}}}}To disable a secret without removing it:
{:api-key :secret/disabled}The tools/ directory contains local Babashka tools installed via bbin:
| Tool | Description |
|---|---|
hello |
Template/example tool |
Tools are installed with :local in manifest.edn:
{:pkg/bbin {:hello {:local "./tools/hello"}}}- Copy
tools/hellototools/mytool - Rename namespace in
src/hello/main.cljtomytool.main - Update
bb.ednwith the new main namespace - Add to manifest:
{:pkg/bbin {:mytool {:local "./tools/mytool"}}} - Run
bootstrap :pkg/bbin
bootstrap