dotman is a tiny Rust TUI dotfiles manager for my personal macOS/Linux environment. It uses a flat YAML config to install software, link config files, create directories, clean old paths, and run setup commands — all in a Plan -> Review -> Run flow with per-machine state and persistent run history.
Try the interactive demo at dotfiles.tabsp.com — the real Ratatui TUI rendered in the browser by a side-effect-free Rust/WebAssembly state machine. Plan, Review, Run, and Replay are fully navigable; nothing ever executes.
| Layer | Tools |
|---|---|
| Terminal | Ghostty |
| Shell | Fish with numbered conf.d modules and Fisher plugins |
| Multiplexer | tmux with tmux-status; herdr |
| Software | Neovim (LazyVim), mise, lazygit, btop, fastfetch, yazi |
| Enhancement · prompt | Starship, tealdeer, markdownlint-cli2 |
| Enhancement · cli | ripgrep, fd, bat, eza, dua-cli, gum, tree-sitter-cli, delta, trash-cli, pay-respects, gitleaks |
| Enhancement · data | fzf, zoxide, jq, yq, direnv, Atuin |
| Font | Maple Mono NF |
Install the latest dotman release with the checksum-verifying installer:
curl -fsSL https://github.com/tabsp/dotfiles/releases/latest/download/install.sh | shThe installer detects macOS/Linux and arm64/x86_64, verifies the release SHA-256,
and installs to ~/.local/bin. Set DOTMAN_VERSION=v0.3.3 to pin a release or
DOTMAN_INSTALL_DIR to choose another directory.
If Homebrew is already available, install and upgrade through the Tap instead:
brew install tabsp/tap/dotmanUpdate an installation created by install.sh in place:
dotman self updateHomebrew owns installations created through the Tap. Upgrade those with:
brew upgrade dotmanTo prevent the two package managers from overwriting each other, self update
detects Homebrew Cellar installations and refuses to modify them.
Then run it. On first launch, dotman initializes the default dotfiles profile and opens the main menu. Choose Deploy, adjust the Plan, then review the selected actions before starting the Run:
dotmanThis clones https://github.com/tabsp/dotfiles.git to
~/.local/share/dotman/repos/main, loads dotman.yaml, and opens the TUI.
For unattended setup (CI, scripted setup):
dotman deploy --headlessHeadless mode uses the same execution and result model as the TUI, prints live
action output and a final ran / changed / no change / failed summary, saves the
run to history, and exits non-zero for failed or aborted runs.
To initialize this repository explicitly:
dotman init https://github.com/tabsp/dotfiles.git --branch main --profile main
dotman deploydotman # TUI main menu (auto-inits on first run)
dotman deploy # TUI: sync → plan → review → run
dotman plan # TUI: show plan only, no execution
dotman init [repo] # initialize a dotfiles profile
dotman sync # git pull current profile
dotman status # show profile and repo state
dotman doctor # check prerequisites
dotman profile list # list configured profiles
dotman profile add <name> <repo>
dotman profile remove <name>
dotman history # TUI: browse past runs
dotman run <ulid> # TUI: replay a past run
dotman new-link <target> <source>
dotman deploy --headless # headless: non-interactive deploy
dotman plan --headless # headless: emit JSON plan to stdoutGlobal options:
--headless no prompts; use safe defaults and fail on ambiguity
--bootstrap-git allow dotman to install git before profile/bootstrap work
--config <path> use a dotman.yaml directly and bypass profile resolution
--no-init fail instead of auto-initializing when no config is foundThe footer shows only the primary keys for the current screen:
| Screen | Primary keys |
|---|---|
| Main menu | ↑↓ navigate, Enter open, q quit |
| Plan | ↑↓ navigate, Space toggle, s save, r review, q back |
| Review | ↑↓ scroll, r run, q back |
| Run / Result | ↑↓ scroll, Tab filter, Enter fold, f follow when paused, q abort/back |
| History | ↑↓ navigate, Enter open, d delete, q back |
| Run replay | ↑↓ navigate, Space fold, q back |
Direct main-menu keys (d deploy, p plan, h history), Vim navigation
(j/k, gg, G), Home/End, PageUp/PageDown, arrow-key filter switching,
and equivalent Enter/Space actions remain available as unlisted convenience
keys.
Result labels are intentional: Ran means a shell command completed; Changed means dotman installed, created, linked, backed up, or cleaned something. Errors are shown before warnings, while the final run result remains visible if saving history fails.
Deployment steps live in dotman.yaml in your dotfiles repo:
package_managers:
macos: brew
ubuntu: brew
arch: pacman
auto_install_pkg_manager: true
default_shell: fish
install:
[ghostty, fish, tmux, neovim, lazygit, btop, ripgrep, fzf, starship, atuin]
links:
~/.config/fish: config/fish
~/.config/nvim: config/nvim
~/.config/ghostty: config/ghostty
~/.tmux.conf: config/tmux.conf
create:
- ~/.config/fish-local
- ~/Workspace/tries
shell:
- command: fish -lc 'fisher update'
description: Sync fish plugins
optional: true
if: command -v fish >/dev/null 2>&1
clean:
- target: ~/.config/old-tool
force: trueYAML field reference:
package_managers— per-platform package manager.auto_install_pkg_manager— if true, attempts to install the package manager itself (e.g. Homebrew) before any install steps.default_shell— login shell to switch to automatically. dotman resolves the real path withcommand -vand ensures it is listed in/etc/shells.install: [name]— list of tool names to install. dotman picks the right install command for your platform from its internal tool database.links:— map of target -> source. dotman also accepts list entries withtarget,source,backup, andrelinkfor per-link behavior.create:— directories to ensure exist.shell:— list of shell commands. Supportsdescription,optional: true, andif:(condition guard).clean:— paths to remove. By default only symlinks are removed; useforce: trueto back up and remove regular files/directories.
Profile configuration (repo URL, branch, checkout path, auto-sync) lives at
~/.config/dotman/config.toml. dotman manages this automatically — you
don't need to create or edit it by hand.
Per-machine selections are stored under
~/.local/share/dotman/selection/, scoped by the normalized dotman.yaml path.
Small config edits therefore keep existing choices; newly added item IDs use
their plan defaults.
Run logs are at ~/.local/share/dotman/runs/<ulid>.json and can be
browsed with dotman history or dotman run <id>.
The deployment pipeline is:
resolve profile → sync repo (git pull) → load dotman.yaml → build plan → review → execute → save history
dotman's profile system manages the dotfiles repo itself (URL, branch, clone
path, auto-sync). The deployment config (dotman.yaml) only describes what
to deploy — installs, links, creates, shell commands.
Auto-init triggers automatically when no profile or config is found. Headless mode uses non-interactive defaults. In the TUI, deployment changes are shown in Plan and Review before Run starts.
Machine-specific paths, tokens, and temporary tool setup should stay out of the shared repository.
Fish loads local-only files from:
~/.config/fish-local/*.fish
For first-time setup on a new machine, follow docs/new-machine.md.
make build
make format # format all supported tracked files
make format-check # check formatting without modifying files
make lint
make secret-check # scan the working tree and Git history for secrets
make test
make config-check # run the complete local configuration validation
make ciBefore committing, format the repository, run the complete configuration validation, and review the resulting diff:
make format
make config-check
git diffPushing a vX.Y.Z tag that matches the version in Cargo.toml builds and tests
all four release targets, publishes their archives, checksums, and install.sh,
then generates, installs, tests, and pushes Formula/dotman.rb to
tabsp/homebrew-tap. Manual workflow runs are only for retrying an existing
semantic release tag and build that tag rather than the selected UI branch.
The Homebrew publication uses a write-enabled SSH Deploy Key scoped to the
public tabsp/homebrew-tap repository. Its private key is stored in this
repository as the HOMEBREW_TAP_DEPLOY_KEY Actions secret; no personal access
token is required.

