Cross-platform dotfiles that bootstrap a fresh macOS or Linux box β from zero to a fully wired Zsh in a single paste.
Tip
Fresh machine? This is the only command you need. It bootstraps everything.
bash -c "$(curl -fsSL https://raw.githubusercontent.com/jonogould/dotfiles/master/install.sh)"That's it. β The installer clones this repo to ~/.dotfiles (over HTTPS β no
SSH keys required), re-execs itself from there, and is 100% safe to re-run.
| π₯οΈ Cross-OS | One installer, identical result on macOS & Linux |
| π Idempotent | Re-run anytime β it heals drift and never clobbers your secrets |
| π§³ Auto-backup | Existing files are stashed in a timestamped folder before linking |
π¨ Gorgeous .env wizard |
A polished Go TUI walks you through your secrets |
| 𧬠Profile provisioning | Rehydrate private details from one YAML file, zero prompts |
| πͺΆ Featherweight clone | Git LFS pulls only the binary your CPU needs |
| π Raspberry-Pi friendly | No bloated downloads, no Go required at install |
flowchart LR
A[π₯ Bootstrap clone] --> B[π§± Install deps]
B --> C[π Symlink configs]
C --> D[βοΈ Post-link setup]
D --> E[π Set Zsh default]
- π₯Ύ Self-bootstrap β clones the repo to
~/.dotfiles(orgit pulls if it already exists), then re-runs from the cloned copy. - π§± Dependencies
- π Symlinks β links config files into
$HOME. Any existing real file is moved to~/.dotfiles-backup-<timestamp>/first, then replaced with a symlink (ln -sfn). - βοΈ Post-link setup
- π antidote β from Homebrew on macOS
(it's in the
Brewfile); on Linux without Homebrew it falls back to a git clone into~/.antidote. Either way it regenerates~/.zsh_plugins.zsh(from~/.zsh_plugins.txt). - π¦ nvm β from Homebrew on macOS (the
installer just ensures the
~/.nvmdata dir exists); on Linux without Homebrew it falls back to the upstream install script into~/.nvm. - π
.envwizard β see Secrets below. - β‘ Recompiles zsh files via
zsh/compile.zsh. - π Sets Zsh as the default shell (only if it's listed in
/etc/shells).
- π antidote β from Homebrew on macOS
(it's in the
| π Source (in repo) | π― Target |
|---|---|
zsh/zshrc |
~/.zshrc |
git/gitconfig |
~/.gitconfig |
editorconfig/editorconfig |
~/.editorconfig |
ack/ackrc |
~/.ackrc |
API keys for AI tooling are sourced by zsh/ai.zsh from
~/.dotfiles/.env β which is π gitignored and never committed.
When run interactively, the installer launches a prebuilt Go TUI
(scripts/envsetup) that walks you through each variable:
- β¨οΈ Pre-fills the existing/template value β just hit Enter to keep it.
- πΆοΈ Masks secret-looking keys (
*TOKEN*,*KEY*,*SECRET*, β¦) as you type. - πΎ Writes
.envatomically with0600perms. - πͺ Works even under
curl | bashbecause it reads from/dev/tty.
Note
When run non-interactively (CI / no tty), if you decline the prompt, or if
git-lfs / the matching binary is unavailable, it gracefully falls back to
copying the template only if .env is missing β so you can fill it in by hand:
cp ~/.dotfiles/.env.example ~/.dotfiles/.env
$EDITOR ~/.dotfiles/.envπ Keys: GOCODE_API_TOKEN Β· OPENAI_API_KEY
Personal & employer-specific details are kept out of version control β
your Git identity, private endpoints, secrets, GOPRIVATE orgs, work aliases
and tool shims live in gitignored local files (git/identity.local,
.env, zsh/local.zsh). To rehydrate them on a fresh machine in one shot,
keep a single private YAML profile (in a password manager / secure note)
and point the installer at it:
DOTFILES_PROFILE=~/secure/dotfiles.profile.yml \
bash -c "$(curl -fsSL https://raw.githubusercontent.com/jonogould/dotfiles/master/install.sh)"When DOTFILES_PROFILE is set, the installer provisions non-interactively
(the .env wizard is skipped) by regenerating each local file from the
profile. See dotfiles.profile.example for the
full schema:
git: # -> git/identity.local
name: Your Name
email: [email protected]
env: # -> .env (overlaid onto .env.example, 0600)
ANTHROPIC_BASE_URL: ""
GOCODE_API_TOKEN: ""
zsh: # -> zsh/local.zsh
GOPRIVATE: "github.com/your-org/*"
extra: |
alias work="cd ~/dev/work"- π The profile is the source of truth β sections it provides are (re)written; sections it omits are left untouched.
- πͺ
.envkeeps the template's order and references (e.g.OPENAI_API_KEYmirrorsGOCODE_API_TOKEN); extra keys are appended. - π‘οΈ Real profiles are gitignored (
dotfiles.profile*) β only the.exampletemplate is tracked.
Important
Profile mode reuses the prebuilt LFS helper, so it needs git-lfs and the
matching binary. If either is missing it warns and falls back to the
interactive flow.
The interactive wizard is a tiny, dependency-light Go program.
Prebuilt binaries for darwin / linux Γ amd64 / arm64 live under
scripts/envsetup/bin/ and are tracked with Git LFS:
- πͺΆ A fresh clone / bootstrap uses
GIT_LFS_SKIP_SMUDGE=1and pulls only LFS pointers (a few KB). - π― The installer then
git lfs pull --includes just the one binary for the current host β a Raspberry Pi never downloads the other platforms' builds.
Built with the pinned Go toolchain go1.25.11 (auto-downloaded via the
toolchain directive). After editing scripts/envsetup/main.go or its deps:
./scripts/envsetup/build.sh # ποΈ cross-compiles all four targets into bin/
git add scripts/envsetup/bin # π¦ LFS-tracked via .gitattributes
git commitImportant
Contributors need Git LFS installed once: git lfs install.
The installer is idempotent β re-running pulls the latest repo, re-links
anything that drifted (backing up first), and re-runs post-link setup without
clobbering your .env. To update later:
bash ~/.dotfiles/install.sh.zwc files are compiled bytecode that speed up shell startup. They're
gitignored and regenerated automatically during install. After editing any
.zsh file, recompile:
zsh ~/.dotfiles/zsh/compile.zshReleased into the public domain under The Unlicense β copy, modify, use, and redistribute it however you like, no attribution required.
Built with π Zsh, πΉ Go, and a lot of β.