dotfiles
Places the raw dotfiles you list in dotfiles_list into $HOME on every apply: links, copies, or templates them, backs up the files it adopts, and prunes links you drop from the list. It folds the job stow or chezmoi apply would do into the run that already provisions the machine.
Demo

What it does
- Links, copies, or templates each entry in
dotfiles_listto its path under$HOME - Creates parent directories as needed, so
.config/starship.tomllands even on a fresh machine - Backs up a pre-existing real file to
<path>.dotpro.bakthe first time it adopts the path - Records managed paths and prunes the symlinks for entries you remove
Variables
| Variable | Default | Description |
|---|---|---|
dotfiles_list | [] | Dotfiles the role places in $HOME (see below) |
dotfiles_dir | | Base directory the dotfiles_list sources are resolved against |
dotfiles_mode | link | Default placement for entries that do not set their own: link, copy, or template |
dotfiles_backup | true | Move a pre-existing real file to <path>.dotpro.bak the first time the role adopts its path |
dotfiles_ignore | [] | Glob patterns (matched on source path and basename) dropped from glob and directory entries |
dotfiles_state | ~/.local/state/dotpro/dotfiles | Manifest of managed paths, used to prune entries you remove |
Managing dotfiles
Not every file wants to be a template. A hand-tuned starship.toml or a long .bashrc is happiest committed verbatim and dropped into $HOME as-is. List those files in dotfiles_list and the role puts them in place on every apply, the job stow or chezmoi apply would do, but folded into the run that already provisions the machine:
dotfiles_list: # default: []
- .bashrc
- .gitconfig
- .config/starship.tomlEach entry is a path relative to dotfiles_dir (the repo root by default) and is linked to the same path under $HOME. Parent directories are created as needed, so .config/starship.toml links into ~/.config/ even on a fresh machine.
Where each file lands
A bare string links to the matching path under $HOME. Use a mapping to send a file somewhere else, or to copy or template it instead of linking:
dotfiles_list:
- .bashrc # ~/.bashrc -> repo/.bashrc
- src: shell/aliases.sh # ~/.config/shell/aliases.sh -> repo/shell/aliases.sh
dest: .config/shell/aliases.sh
- src: gitconfig.j2 # rendered through Jinja, not linked
dest: .gitconfig
mode: template # default: dotfiles_mode (link)
- src: ssh_config # some apps reject a symlinked config; copy it
dest: .ssh/config
mode: copylink points back at the repo, so editing the source and re-applying needs no re-render. copy and template write a real file (template runs it through Jinja with the play's variables first), for the handful of apps that refuse to follow a symlinked config.
Globs and whole directories
A src containing a glob character (*, ?, [) expands per file; a plain directory path is placed whole as a single link. dotfiles_ignore drops matches by path or basename:
dotfiles_ignore: # default: []
- "*.md"
dotfiles_list:
- .config/nvim # the whole directory, one symlink
- shell/*.zsh # each match mirrored to ~/shell/<name>.zsh
- src: bin/* # reparented: each match -> ~/.local/bin/<name>
dest: .local/binAdoption and pruning
The role manages a set, not just a list, so it reconciles $HOME to what you declare:
- The first time it takes over a path that already holds a real file (the distro's default
.bashrc, say), it moves that file to<path>.dotpro.bakbefore placing its own, unlessdotfiles_backupisfalse. Paths it already manages are updated in place, so the backup happens once, on adoption. - It records every managed path in
dotfiles_state. Drop an entry from the list and the next apply removes the symlink it left behind. A droppedcopyortemplatetarget is a real file, so it is reported for you to delete by hand rather than removed automatically.
Files you would rather drive from data (a .gitconfig keyed on per-machine identity, for one) belong in a role instead; the git role is built for exactly that. For secrets, reach for Ansible Vault and the bitwarden role rather than committing them as plain dotfiles, and for per-host variants set dotfiles_list differently in host_vars or behind a when.
Combining with settings roles
Most config in this collection is driven by data, not files: the git role builds ~/.gitconfig from git_config, starship renders ~/.config/starship.toml from starship_config, and claude merges claude_code_settings into ~/.claude/settings.json. That is the "settings, not files" path, and it is the one to reach for first: you get merged defaults, cross-platform behavior, and per-host overrides from a single declaration.
This role is the other path, a file committed verbatim and linked into place. The two are complementary and are built to coexist on the same path.
Write-through is the default
The collection's config roles are built to share a path with this one. They write through a symlinked target instead of replacing it, so linking a file with the dotfiles role and also driving it from a role's vars lands the role's output in the linked repo file, ready to commit:
git config(thegitrole) resolves the symlink natively and writes to the link target.- The
templateandcopywriters (starship,claude,kubernetes) setfollow: true, so they too edit through the link. The roles that merge (claude,kubernetes) read the same path back first, so your hand edits in the repo file are preserved and merged, not overwritten.
Order still matters: place the dotfiles role before the settings roles in the play so the link already exists when they write. If a settings role runs first on a fresh machine and the file is not there yet, it creates a regular file, and the later dotfiles step links over it.
Even with write-through, a path still has one logical source of truth. Two clean choices and one hybrid:
- Settings, not files drive the values through the role's vars and leave the file out of
dotfiles_list. The rendered file is disposable output. - Verbatim file list the file in
dotfiles_listand do not set a role's vars for it. The committed file is the source of truth. - Hybrid (write-through capture) link the file and drive it, accepting mixed ownership for the benefit that edits made outside Ansible are captured into the repo.
For files no role manages (a hand-tuned .bashrc, .config/nvim, AGENTS.md), there is no question to answer, and this role is simply the tool.
Commands
The dotpro role ships dotpro dotfiles, which lists each managed dotfile and its link state (ok / broken / file / missing); dotpro dotfiles status previews drift with a --check apply of the dotfiles tag.