chris/dotfiles
publicClone
Push over the same URL. A password will not work: create a token under access tokens and use it in place of one.
Get started with your own dotfiles.
- .config
- .github
- art
- bin
- .denrc 680 B
- .gitignore 79 B
- .gitignore_global 479 B
- .macos 44.2 KB
- .zshrc 1.9 KB
- aliases.sh 3.8 KB
- apps.yaml 2.0 KB
- clone.sh 2.3 KB
- deps.yaml 1.4 KB
- env.sh 2.1 KB
- fonts.yaml 336 B
- fresh.sh 12.6 KB
- LICENSE.md 1.0 KB
- mail-accounts.env.example 2.2 KB
- package.json 587 B
- README.md 15.7 KB
- ssh.sh 3.1 KB
- zed.json 1.2 KB

Chris's Dotfiles
My personal macOS setup. It takes the manual labor out of provisioning a new Mac and keeps every machine I use consistent and reproducible.
The modern stack:
| Concern | Tool | Replaces |
|---|---|---|
| Shell | Den | zsh + oh-my-zsh |
| Prompt | Den native prompt (.config/den.jsonc) | starship |
| Shell plugins | Den native features | zsh-autosuggestions, zsh-syntax-highlighting, fast-syntax-highlighting, zsh-autocomplete |
| CLI tools, GUI apps, fonts, other deps | Pantry (deps.yaml + apps.yaml/fonts.yaml) | Homebrew Brewfile + casks |
Credentials, .env & app-settings sync | ts-backups (.config/backups.ts → iCloud) | mackup |
Everything Pantry can install — including Zig, Bun, Den's build toolchain — is
declared across deps.yaml (CLI tools + Zig & more) and the sibling
apps.yaml / fonts.yaml (GUI apps + fonts), all
installed with a single pantry install.
How Den is configured
Den splits configuration into two files, both symlinked from this repo:
.denrc→~/.denrc— a startup script, sourced line-by-line like.zshrc. It justsources the two shell-neutral files that both Den and the zsh fallback share:env.sh(environment +$PATH) andaliases.sh(aliases)..config/den.jsonc→~/.config/den.jsonc— the declarative config (JSONC). Holds the prompt format, syntax highlighting, inline autosuggestions, completion and history search. This is what replacesstarship.tomland the cloned zsh plugins.
Both shells source the same env.sh / aliases.sh, so there is a single source
of truth — no duplicated $PATH or alias lists between Den and zsh.
Repository layout
| Path | Purpose |
|---|---|
.denrc | Den startup script: env, $PATH, aliases (primary shell) |
.config/den.jsonc | Den declarative config: prompt, highlighting, completion |
.zshrc | Trimmed fallback zsh config with an opt-in exec den |
env.sh | Environment + $PATH, shared by both shells (POSIX-sh) |
aliases.sh | Aliases, shared by both shells (POSIX-sh) |
deps.yaml | CLI dependencies installed by Pantry (incl. Zig); uses the deps: shorthand |
apps.yaml | GUI apps Pantry installs on macOS (Pantry registry domains + Mac App Store) |
fonts.yaml | Fonts Pantry installs on macOS |
.config/backups.ts | What gets synced to iCloud: credentials, project .envs, app settings (ts-backups) |
bin/dotsync | Runs ts-backups (from source) against .config/backups.ts |
bin/git-sync.ts | Rescues/recovers local-only git work (unpushed commits, stashes, uncommitted, untracked) |
bin/dot-recover | One-shot new-machine recovery: secrets + repos + git work + mail accounts |
bin/mail-profile | Generates a Mail.app config profile (.mobileconfig) from a .env of mail creds |
mail-accounts.env.example | Template for the mail-account credentials bin/mail-profile reads |
package.json | bun run backup / rescue / recover / prewipe / mail scripts |
fresh.sh | One-shot provisioning script for a new Mac |
clone.sh | Clones every repo in the stacksjs / home-lang / cwcss / zig-utils orgs |
ssh.sh | Generates a new SSH key for GitHub |
.macos | macOS defaults tweaks |
zed.json | Zed editor settings |
Fresh macOS setup
1. Back up the old machine first
- Run a full off-machine sync to iCloud:
cd ~/.dotfiles && bun run prewipe. This snapshots credentials, project.envs and app settings and rescues all your local-only git work — unpushed commits, stashes, uncommitted changes, untracked files and any no-remote repos (replaces the oldmackup backup, and means you no longer have to manually push every branch/stash first). - Wait for iCloud to finish uploading (see the warning under Backups & restore) before erasing.
- Save anything still not covered (large local databases, other app data, etc.).
2. Provision the new machine
Update macOS to the latest version (System Settings → Software Update).
Generate a new SSH key for GitHub:
curl https://raw.githubusercontent.com/chrisbbreuer/dotfiles/HEAD/ssh.sh | sh -s "<your-email-address>"Add the printed public key at https://github.com/settings/keys.
Clone this repo to
~/.dotfiles:git clone git@github.com:chrisbbreuer/dotfiles.git ~/.dotfilesRun the installer:
cd ~/.dotfiles && ./fresh.shfresh.shis idempotent and will:- install Pantry (the package manager),
- install all dependencies (
pantry install) — CLI tools and Zig fromdeps.yaml, plus GUI apps and fonts fromapps.yaml/fonts.yaml, - clone and build Den, then symlink it to
~/.local/bin/den, - symlink
~/.denrc,~/.config/den.jsonc(Den) and~/.zshrc(fallback), - recover everything from iCloud (
bun run recover— see Backups & restore): credentials, project.envs and app settings, then every repo cloned back to its original~/Codepath with all your local-only git work (unpushed commits, stashes, uncommitted, untracked), then any remaining org repos, then your mail accounts (generates the config profile and opens it + Internet Accounts to finish — see Mail accounts), - apply macOS defaults (
.macos).
Recovery needs iCloud signed in and
ghauthenticated. If either isn't ready,fresh.shskips it with a printed follow-up — re-runbun run recoveronce iCloud has synced (andgh auth loginif cloning needs it).Restart to finalize.
The GUI apps and fonts are installed by
pantry installin step 4 — the lists live inapps.yamlandfonts.yaml, which Pantry reads automatically alongsidedeps.yaml. Apps and fonts come from Pantry's own registry (registry.pantry.dev) — each is a Pantry package keyed by domain (e.g.ghostty.org), downloaded and installed directly. No Homebrew, nobrew, nothing third-party. Mac App Store apps (themas:entries) need no extra tooling either — Pantry installs them silently and natively via Apple's CommerceKit (the rolemasplays elsewhere, but with nomasbinary and no App Store window): it skips any app already in/Applications, and for the rest drives the same download the App Store would, then installs the package with/usr/sbin/installer. That last step needs root, sofresh.shprimessudofirst; if you're signed into the App Store and approve sudo, the apps just appear. Anything it can't install silently (not signed in, or a paid app you don't own) falls back to opening the App Store.
3. Start using Den
Open a new terminal and run den. To have your terminal launch Den directly,
point its shell/command setting at ~/.local/bin/den, or uncomment the opt-in
exec den line at the bottom of .zshrc.
To make Den your login shell:
echo "$HOME/.local/bin/den" | sudo tee -a /etc/shells
chsh -s "$HOME/.local/bin/den"Day-to-day
- Install a CLI tool: add it under
deps:indeps.yaml, thenpantry install. - Install a GUI app or font: add a line to
apps.yaml(a Pantry domain like- cursor.com, or{ mas: "<id>", name: <App> }for a Mac App Store app) orfonts.yaml(e.g.- inter), thenpantry install. Verify a package exists atregistry.pantry.dev/binaries/<domain>/metadata.json. - Sync a new secret/setting: add it (or a new app's config) to
.config/backups.ts, thenbun run backup. - Add an alias: edit
aliases.sh(loaded by both shells), thenreloadshell. - Change env /
$PATH: editenv.sh(loaded by both shells), thenreloadshell(exec $SHELL). - Change prompt / highlighting / completion: edit
.config/den.jsonc(hot-reloads automatically). - Update / change Zig: edit the
ziglang.orgpin indeps.yaml, thenpantry install. - Rebuild Den after pulling:
cd ~/Code/den && zig build -Doptimize=ReleaseFast.
The prompt
Configured in .config/den.jsonc via prompt.format — no starship. Placeholders
include {path}, {git}, {symbol}, {modules}, {exitcode} and per-language
modules like {bun}, {node}, {zig}, {python}. Tweak to taste.
Native plugins
Den's autosuggestions, syntax highlighting, completion and fuzzy history search
are built in — toggled via line_editor, completion and history in
.config/den.jsonc. There are no cloned zsh plugin repos to maintain. To develop
your own Den plugin, see Den's docs/PLUGIN_DEVELOPMENT.md.
Backups & restore
Everything that a macOS wipe would otherwise destroy is synced to iCloud Drive
(~/Library/Mobile Documents/com~apple~CloudDocs/ts-backups) — so it survives the
wipe, syncs across machines, and never touches this public git repo. There are
two halves:
- Secrets & settings — credentials, project
.envs and app settings, via ts-backups (the mackup successor), declared in.config/backups.ts. - Local-only git work — unpushed commits, every stash, uncommitted changes,
untracked files, and repos with no remote, via
bin/git-sync.ts. These exist nowhere else and re-cloning from GitHub will not bring them back.
Before a wipe
cd ~/.dotfiles
bun run prewipe # = backup (secrets/.env/settings) + rescue (all local git work)Or run the halves separately: bun run backup and bun run rescue.
bun run list shows what's in iCloud.
⚠️ Wait for iCloud to finish uploading before you wipe. These files are written locally first; iCloud uploads them in the background and there's no reliable CLI to confirm completion. Check Finder (the
ts-backupsfolder shows no pending-upload arrows) or System Settings → your name → iCloud before erasing.
After a reinstall — one command
cd ~/.dotfiles && bun run recoverrecover (bin/dot-recover) does the whole thing: restores
credentials/.envs/settings, clones every repo back to its original ~/Code
path and replays your unpushed commits, stashes, uncommitted changes and
untracked files, clones any remaining org repos, then sets up your mail
accounts (see Mail accounts). fresh.sh calls it for you;
run it by hand if iCloud wasn't synced yet at provision time.
What's covered (secrets & settings)
- Credentials/profile —
~/.ssh(keys, with perms preserved),~/.aws,~/.config/gh,~/.config/github-copilot,~/.npmrc,~/.docker/config.json,~/.config/composer. - Git identity —
~/.gitconfig,~/.config/git. - App settings — VS Code / Cursor
settings.json+keybindings.json+snippets,~/.config/zed,~/.config/raycast(extensions excluded). - Claude Code —
~/.claudesettings, any custom commands/agents, and your per-project memory (projects/<slug>/memory/**); the ~2GB of regenerable session data (transcripts, file-history, caches) is excluded. - Project secrets — every real
.env/.env.*under~/Code(recursively, paths preserved;.env.exampleand friends skipped). - Mail accounts —
~/.config/mail-accounts.env(the credsbin/mail-profileturns into a Mail.app profile — see Mail accounts).
Each entry is its own timestamped, retained snapshot, so you can restore one thing in isolation:
./bin/dotsync restore --only ssh --overwrite # just the SSH keys
./bin/dotsync restore --only project-envs --overwrite # just the project .env filesHow the git rescue works
bin/git-sync.ts rescue walks ~/Code and, for each repo, bundles only the
objects that aren't already on a remote (git bundle --all --not --remotes) plus
every stash and your uncommitted/untracked files; a repo with no remote is bundled
in full. It records a manifest of all repos and their remotes so recover can
rebuild your exact layout. Nothing is pushed and your working repos are left
untouched. Restore is non-destructive: bundled refs land under refs/rescued/*,
branches only fast-forward, and a diverged branch is flagged for you to merge by
hand.
ts-backups runs from source via
bun(it isn't published to npm).bin/dotsyncfinds your local checkout —~/Code/Libraries/ts-backups,~/Code/ts-backups, or~/Code/stacksjs/ts-backups— and clones it if missing.
Note: secrets live in your iCloud, which you're trusting with them. Your
ghtoken lives in the macOS keyring (not a file), so it doesn't transfer — on a new machine the SSH keys cover git, andgh auth loginre-auths the CLI.
Mail accounts
macOS has no supported CLI to create a Mail.app account directly, but a
configuration profile can define IMAP/SMTP accounts (servers, ports, SSL,
username, password) declaratively. bin/mail-profile
generates that profile from a .env of credentials and opens it for you:
cp mail-accounts.env.example ~/.config/mail-accounts.env # then fill it in
bun run mail # generates + opens the profileEach account is one block in the .env (see
mail-accounts.env.example); it's synced to iCloud
by ts-backups (the mail-accounts entry in .config/backups.ts) and never
committed to this repo. On a new machine bun run recover (and fresh.sh, which
calls it) restores this file and then runs bun run mail for you automatically —
no extra step.
Every account is pre-filled as long as it has a password — custom IMAP/SMTP
take a server block; Gmail and iCloud just need an email + an app password
(myaccount.google.com → App passwords; account.apple.com → App-Specific
Passwords), with their servers preset. All pre-filled accounts go into one
configuration profile. macOS won't install a profile silently on a non-MDM Mac,
so bun run mail opens it and you approve it once in System Settings → General →
Device Management — every account lands from that single click, no per-account
sign-in.
An account with no password (or <PREFIX>_INTERACTIVE=true) falls back to a
one-time interactive sign-in: bun run mail opens System Settings → Internet
Accounts and tells you which to add. Use this only when you can't get an app
password.
The generated
.mobileconfigholds plaintext passwords (written to$TMPDIR,chmod 600) — delete it once the profile is installed.
Cleaning your old Mac (optional)
After the new Mac is set up you can erase and reinstall macOS on the old one. Make sure you backed everything up first.
Thanks
Originally based on Dries Vintens's dotfiles, with inspiration from Zach Holman, Mathias Bynens and the GitHub does dotfiles project. Banner by Caneco. Thanks to everyone who open-sources their dotfiles.