Skip to content

Where things live

dotty splits state across three places with three different privacy levels: your dotfiles repository (shareable), public config under $XDG_CONFIG_HOME (safe to commit, safe to show), and private data under $XDG_DATA_HOME (secrets and personal state, never in a repo). Knowing which is which is most of the mental model.

The repository

dotty init scaffolds (or adopts) a dotfiles repository — by default <repos-dir>/dotfiles, recognised later by its .dotty-version marker:

dotfiles/
├── .dotty-version            # marks this repo as dotty-managed
├── Brewfile                  # composed from brewfile.d for the profile
├── brewfile.d/               # per-component Brewfile fragments
├── profiles/                 # one directory per profile
│   ├── personal/
│   │   ├── profile.json      # metadata + the profile's stored init answers
│   │   ├── Brewfile          # the profile's package set
│   │   ├── env.zsh           # profile-varying files (also git.gitconfig,
│   │   │                     #   worktrees.gitconfig)
│   │   └── home/             # profile-varying $HOME entries
│   └── work/
└── home/                     # everything below maps into $HOME
    ├── .config/
    │   └── zsh/  git/  ghostty/  tmux/  nvim/  …
    └── .ssh/config           # when security keys are enabled

dotty dotfiles link symlinks the home tree into $HOME using symlink-farm folded links; dotty dotfiles status shows what is linked, missing, or conflicting.

Profiles travel with the repo — they describe machine classes (personal, work), not individual machines. See Profiles.

Public config — $XDG_CONFIG_HOME/dotty

Path Purpose
~/.config/dotty/<profile>/ Symlinks into the repo's profiles/<name> directories
~/.config/dotty/active-profile Symlink to the active profile — the only machine-local piece of profile state

Nothing here is secret: it is symlinks into your dotfiles repo plus one more. Retargeting active-profile (dotty profile activate) atomically swaps every per-profile rendered file at once.

Private data — $XDG_DATA_HOME/dotty

Created 0700, contents 0600. This is personal, per-machine state that must never land in a repository:

Path Purpose
~/.local/share/dotty/ Signing-key stubs (the non-secret halves of YubiKey-resident SSH keys)
~/.local/share/dotty/backups/<timestamp>/ Timestamped backups taken when linking with --on-conflict=backup — recover with dotty dotfiles restore
~/.local/share/dotty/dotty-ssh-askpass The askpass applet symlink that routes OpenSSH PIN prompts to pinentry-mac
~/.local/share/dotty/private/<profile>/ Decrypted plaintext of the encrypted private repository, linked into $HOME through the private/active-profile symlink

Security-key serial→alias mappings also live on the private side — key serials identify your hardware and stay out of the public repo.

PII — ~/.config/private/git/config

Your git user.name / user.email (and the commit.gpgSign default) are written once by dotty init to ~/.config/private/git/config, which the shared git config includes. It is never overwritten on re-runs and never part of the dotfiles repo — so the repo can be public without leaking who you are.

The scalable form of this is the encrypted private repository: a second repo, marked by .dotty-private, whose per-profile trees hold git identities, ssh host blocks, and the rest of your PII as age ciphertext keyed to your YubiKeys. When a profile records one (privateRepo in its answers), dotty private link provides ~/.config/private/git/config per profile and init skips its own write.

Keychain

Secrets managed by dotty env don't live on disk at all — they are items in the macOS login Keychain.