dots is a fast, cross-platform dotfiles manager with an interactive TUI, written in Rust. It installs your tools, wires up symlinks GNU Stow–style, applies premade app configs, and keeps everything healthy — on macOS (Homebrew) and Linux (apt/dnf) alike.
This repository is the
dotstool itself. Your actual dotfiles/configs live in a separate repo (e.g.dotfiles-CUK);dotsmanages the symlinks between them and your$HOME.
- Interactive TUI — run
dotsfor a dashboard covering symlink health, installed tools, shell plugins, app configs, and updates. - Cross-platform installs — one dependency list, resolved per platform via Homebrew,
apt, ordnf. - Symlink management — declare your own links (
dots link add), then create/repair them idempotently. Adopts existing files with automatic backups. - App configs — every directory in your dotfiles repo (
nvim/,git/,bat/, …) shows up in the TUI's Configs screen with an install-status badge ([ installed ]/[ partial ]/[ not installed ]); view a config's files, preview their contents, and install/remove it by (un)linking into$HOME. - Premade configs — bundled starter configs for Ghostty, Neovim, and opencode, applied on demand (existing files are backed up).
- Portable profiles — export your setup to
personal.jsonand re-import it on another machine, locally or straight from GitHub. - Self-updating — built-in update checker and one-command upgrade.
- Single static binary — no runtime dependencies (pure-Rust TLS, no OpenSSL/keychain), optimized for size.
Release-by-release history lives in CHANGELOG.md.
curl -fsSL https://raw.githubusercontent.com/CtrlUserKnown/dots/main/install.sh | shThe installer clones the repo to ~/.dots, downloads a prebuilt binary for your OS/arch (or builds from source with cargo if no release matches), puts dots on your PATH, and initializes config.
install.sh [--version <tag>] [--dir <path>]
The same script is mirrored on the site, if you prefer the shorter URL:
curl -fsSL https://ctrluserknown.github.io/dots/install.sh | shPrefer to build it yourself? See BUILD_MACOS.md, or from a clone:
cargo build --release # binary at target/release/dotsTo remove it: uninstall.sh.
Run dots with no arguments to open the TUI. The dashboard's panes — Symlinks, Tools, Zsh, Configs, Update, Network — drill into full-screen views for health checks, aliases, profile, theme, and settings. Lua plugins can add their own panes here.
Everything is also scriptable via subcommands:
| Command | What it does |
|---|---|
dots health [--fix] |
Check and repair all declared symlinks, tools, and plugins |
dots update |
Check for and apply updates |
dots install <name> |
Install a single dependency |
dots install --all |
Install all missing required dependencies |
dots install --optional |
Install all missing optional dependencies |
dots aliases list | add <name> <value> | remove <name> |
Manage shell aliases |
dots premade list | apply | remove <app> |
List/apply/remove bundled starter configs (ghostty, neovim, opencode) |
dots config list | view | install | remove <name> |
View and (un)install app configs discovered in your dotfiles repo |
dots link add <source> <target> |
Adopt a file/dir and symlink it (recorded in links.toml) |
dots link list | apply | remove <target> |
Inspect, create/repair, or remove declared links |
dots profile generate [path] |
Export your setup to personal.json |
dots profile import <path> |
Import a personal.json from a local file |
dots profile import-git <user/repo/path.json> |
Import a personal.json from GitHub |
dots plugins list | new <name> | dir |
Manage Lua plugins that add dashboard panes |
dots init [--quiet] |
Initialize config (idempotent; run automatically by the installer) |
dots --version |
Print the version |
The dependency list is defined in crates/dots/src/packages.rs in three tiers, each mapped to its brew / dnf / apt package name:
| Category | When installed | Examples |
|---|---|---|
| Required | dots install --all |
git, eza, bat, fd, fzf, fastfetch, zoxide |
| Optional | dots install --optional |
neovim, herdr, btop, lazygit, yazi, carapace |
| Dev | dots install <name> |
go, lua, cmake, gcc, ripgrep, gh, docker, ffmpeg, … |
-
~/.dots/— the tool's home (repo checkout +bin/dots). -
~/.dots/settings.toml— tool settings, under a[dots]table:Key Default Meaning update_checktrueCheck for updates periodically update_frequency1440Minutes between update checks greetingtrueShow the greeting banner developer_modefalseEnable developer features theme(empty) Selected theme -
~/.personal/— your personal, machine-local layer:aliases.zsh(sourced after the built-in aliases),apps/, and an optionalconfig.tomlthat overridessettings.toml.
Drop *.lua files in ~/.dots/plugins/ to add your own panes to the dashboard — GitHub, AWS, or anything a shell command can report. Plugins are loaded at startup, run on the TUI thread, and each pane refreshes on its own interval. A plugin that fails to load is reported by dots plugins list and skipped; it never crashes the TUI.
Scaffold one with dots plugins new <name>, then edit it. Two globals are available:
ui — register panes and shape the dashboard:
| Call | Purpose |
|---|---|
ui.pane{ … } |
Register a dashboard pane (see fields below) |
ui.layout{ columns = N } |
Set the dashboard's column count (default 2) |
ui.pane fields — render is the only one you always want:
| Field | Default | Meaning |
|---|---|---|
id |
auto | Stable identifier (shown by dots plugins list) |
title |
id |
Pane title |
render |
— | function() → lines returning a string (split on newlines) or a table of strings |
size |
1 |
Row-height weight — 2 makes the pane twice as tall |
span |
1 |
Columns spanned — 2 makes it full-width in a 2-col grid |
refresh |
30 |
Seconds between render() calls |
on_enter |
— | function() run when the pane is selected with enter / click |
dots — helpers for integrations:
| Call | Returns |
|---|---|
dots.sh(cmd) |
Trimmed stdout of sh -c cmd (drives gh, aws, …; "" on failure) |
dots.env(name) |
An environment variable ("" if unset) |
dots.dir() |
The ~/.dots path |
A minimal example:
ui.pane{
id = "github", title = "GitHub", span = 2, refresh = 120,
render = function()
local prs = dots.sh("gh pr list --author @me --state open | wc -l | tr -d ' '")
if prs == "" then return { "gh not available" } end
return { prs .. " PR(s) open" }
end,
}Ready-to-use examples live in examples/plugins/ (github.lua, aws.lua) — copy one into ~/.dots/plugins/ and run dots.
A Cargo workspace with two crates:
crates/dots/— thedotsbinary: CLI, TUI screens, installer, symlink/link engine, config, the Lua plugin host, and bundled premadeassets/.crates/tui-core/— shared TUI chrome (header/footer/description bars, color theme, flash model) used by thedotsscreens.
cargo test --all # unit + integration tests
cargo clippy --all -- -D warnings # lints
cargo fmt --all -- --check # formatting
bash tests/integration/test_setup.sh # shell integration test (matches CI)An isolated container test environment lives in test-env/ (see test-env/manage.sh); the manual QA checklist is in docs/manual-test-checklist.md. CI runs on Linux and macOS via .github/workflows/ci.yml.
See LICENSE.