dfl is a Go-based runtime for bootstrapping and maintaining a dotfiles repo.
It is designed to replace an ad hoc shell framework with a single binary that can:
- run repo setup from a stable entrypoint
- install named dotfiles components
- install GitHub release binaries
- provide reusable runtime commands for installer scripts
- bootstrap from a minimal shell installer
- self-update from GitHub releases
To set up dfl for your dotfiles repo, start from the dotfiles repo bootstrap script.
Example using elentok/dotfiles/bootstrap:
mkdir -p ~/.dotfiles
cd ~/.dotfiles
curl -fsSL https://raw.githubusercontent.com/elentok/dotfiles/main/bootstrap -o bootstrap
chmod +x bootstrap
./bootstrapThat dotfiles-side bootstrap flow:
- resolves the repo root from the script location
- installs
dflto~/.local/bin/dflif it is missing - runs
dfl setup --repo <repo-root>
This repository also ships the public installer shim used by bootstrap:
./install-dfl.shThat command is for installing the dfl binary itself. After dfl is installed, the normal maintenance entrypoint is:
dfl updateRun the repo setup script:
dfl setupTarget a specific repo explicitly:
dfl setup --repo ~/.dotfilesInstall one or more components from the resolved repo:
dfl install fish
dfl install fish nvim git
dfl i tmuxPreview the update flow without making changes:
dfl --dry-run update --repo ~/.dotfilesShow the resolved repo root:
dfl repo-rootPrint the binary version:
dfl versiondfl also exposes lower-level runtime commands intended for use by component installer scripts.
Examples:
dfl has-command git
dfl os is-mac
dfl pkg brew install ripgrep
dfl pkg github install elentok/gx elentok/colr
dfl symlink tmux.conf ~/.tmux.conf
dfl copy config.toml ~/.config/myapp/config.toml
dfl inject core/ai/AGENTS.md ~/.codex/AGENTS.md
dfl inject --link core/ai/AGENTS.md ~/.codex/AGENTS.md
dfl merge-json ~/.claude/settings.json settings.base.json ~/.claude/settings.json
dfl mkdir ~/.config/myapp
dfl backup ~/.gitconfig
dfl shell "Reload config" -- sh -c 'echo hello'
dfl confirm "Overwrite existing config?"These commands are designed to produce consistent step-style output and to support --dry-run.
dfl inject <source-file> <target-file> appends a managed injected block to the target file using
HTML comment markers, and replaces that managed block on rerun instead of duplicating it.
Use dfl inject --link <source-file> <target-file> to inject a reference payload line in the form
@/absolute/path/to/source instead of file contents.
dfl merge-json <input...> <output> deep-merges two or more JSON files into the output file (the
last argument). Objects merge recursively, arrays are unioned (deduplicated, first-seen order), and
on scalar conflicts the later input wins. The output path may be one of the inputs for an in-place
merge, e.g. dfl merge-json ~/.claude/settings.json settings.base.json ~/.claude/settings.json
keeps runtime-written keys while letting the managed base win the keys it declares.
dfl confirm <question> [yes|no] prompts for a single-keypress yes/no answer and exits 0 for
yes or 1 for no, so it slots straight into shell conditionals:
if dfl confirm "Do something?"; then
# do it
fiThe default is no (Question (y/N)?); pass yes to default to yes (Question (Y/n)?). Pressing
Enter accepts the default, Esc/Ctrl-C/Ctrl-D cancel (treated as no), and any other key is ignored.
The prompt is written to stderr, and when stdin is not a terminal the default is used without
blocking.
GitHub package installs use the repository basename as the binary name and install into
~/.local/bin. For example:
dfl pkg github install elentok/gxThat installs the release binary to a versioned path like ~/.local/bin/gx-v1.2.3, points
~/.local/bin/gx at it via symlink, and keeps the newest three installed versions for rollback.
Releases are published for:
- macOS
amd64 - macOS
arm64 - Linux
amd64 - Linux
arm64
Release archives are named like:
dfl_Darwin_arm64.tar.gz
dfl_Linux_x86_64.tar.gz
dfl can generate shell completion scripts via the completion subcommand.
Fish:
dfl completion fish > ~/.config/fish/completions/dfl.fishBash:
dfl completion bash > /etc/bash_completion.d/dflZsh:
dfl completion zsh > "${fpath[1]}/_dfl"Run the test suite:
make testBuild locally:
go build ./...The release pipeline injects the version string with GoReleaser using -X dfl/internal/buildinfo.Version=<tag>.
dfl setupexpects a dotfiles repo layout with a repo-specificcore/setupentrypoint.dfl installresolves components from the target repo and executes theirinstallscripts.- GitHub-installed binaries use a managed
~/.local/bin/<binary>-<version>plus~/.local/bin/<binary>symlink layout.
