- Rust 100%
|
|
||
|---|---|---|
| cfg | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
dotdotdot
A variable-based dotfile manager. Define your configs once as templates, deploy them to any machine by setting a handful of key/value pairs.
How it works
Your dotfiles live in a git repository. dotdotdot clones that repo, processes each file through a template engine, and writes the results to your home directory. Variables and conditional blocks let a single set of templates produce the right output on every machine.
The typical workflow:
- Put your key/value pairs in
~/.config/dotdotdot/config.toml(font_size,theme,laptop, etc.) - Run
dotdotdot apply.
Profiles are an optional extra for switching between desktop environments or window managers on the same machine (hyprland, plasma, gnome…). They are not required for basic use.
Installation
cargo install --path .
Quick start
# First run - launches the interactive setup wizard
dotdotdot
# Or set up directly
dotdotdot init --address https://github.com/you/dotfiles
# Deploy your dotfiles (uses config [variables])
dotdotdot apply
# Override a specific variable for this run only
dotdotdot apply --override font_size=14
# Switch to your plasma profile
dotdotdot apply --profile plasma
Config file
~/.config/dotdotdot/config.toml - created by dotdotdot init.
[repository]
path = "/home/you/.dotdotdot"
address = "https://github.com/you/dotfiles"
[general]
# Optional: default profile to use when none is passed on the CLI
# profile = "hyprland"
[variables]
# Your machine-specific key/value pairs.
# Strings need quotes; booleans and integers do not.
font_family = "JetBrains Mono"
font_size = 13
theme = "catppuccin-mocha"
laptop = false
natural_scroll = false
Template syntax
Marker lines use a leading comment character followed by .... The same syntax works for any file type. dotdotdot looks for ... and ignores whatever comment character precedes it:
# ... if <expr> ← bash, zsh, toml, yaml, python …
// ... if <expr> ← jsonc, JS …
-- ... if <expr> ← lua, sql …
Conditional blocks
export EDITOR=nvim
# ... if laptop
export HIDPI=1
# ... else
export HIDPI=0
# ... fi
Blocks nest to any depth.
Line output
-- ... line TEMPLATE emits TEMPLATE as a content line with variable substitution applied. The entire marker is a comment, meaning no dummy values, and nothing for formatters to touch:
-- ... line natural_scroll = {{natural_scroll}},
-- ... line output = "{{monitor_output}}",
Use this for bare values (booleans, numbers) where putting {{varname}} directly in the code would be reformatted by a language formatter. Line markers respect conditional blocks:
-- ... if laptop
-- ... line natural_scroll = {{natural_scroll}},
-- ... fi
End-of-line annotation
content <comment_token> ... varname replaces the value on the line with the named variable. Quote style of the original value is preserved. The source value is what linters and formatters see, making it useful when you want a meaningful default:
natural_scroll = false, -- ... natural_scroll
output = "DP-1", -- ... monitor_output
scale = 1, -- ... monitor_scale
If the variable is unset, the line is output as-is (the source value acts as the fallback).
Inline substitution
{{varname}} for user-defined variables, {{$varname}} for built-ins. Best used inside quoted strings where formatters won't interfere:
font_family = "{{font_family}}"
background = "{{bg_color}}"
host = "{{$hostname}}"
Spaces inside the braces are tolerated: {{ varname }} works identically.
Unknown variables are left as-is.
Condition expressions
| Expression | Meaning |
|---|---|
varname |
true if the user variable is set and not empty, false, or 0 |
!varname |
logical negation of a user variable |
$varname |
truthy check for a built-in variable |
!$varname |
logical negation of a built-in variable |
varname == "value" |
user variable equality |
varname != "value" |
user variable inequality |
$var == "value" |
built-in variable equality |
$var != "value" |
built-in variable inequality |
Built-in variables
Available in conditions as $name and in substitution as {{$name}}.
| Name | Value |
|---|---|
hostname |
current machine hostname |
os |
linux, macos, windows, etc. |
arch |
x86_64, aarch64, etc. |
Profiles
Profiles let you switch between different setups on the same machine. E.g. different window managers or desktop environments. They are not needed for managing per-machine differences; that's what [variables] in the config are for.
Create dotdotdot-profiles.toml in your dotfile repository:
[hyprland]
wm = "hyprland"
bar = "waybar"
compositor = "wayland"
[plasma]
wm = "kwin"
bar = "plasma-panel"
compositor = "wayland"
Profile variables override config [variables]. A default profile can be set in [general] so you don't have to pass it every time.
Variable priority
From lowest to highest:
- Built-ins (
$hostname,$os,$arch) - Config
[variables] - Profile variables (
--profile) --overrideflags
Commands
dotdotdot-native
| Command | Description |
|---|---|
dotdotdot |
Interactive setup wizard |
dotdotdot init --address <url> |
Clone repo and write initial config |
dotdotdot apply |
Process templates and write files to $HOME |
dotdotdot add <file…> |
Copy files from $HOME into the repo and stage them |
dotdotdot rm <file…> |
Remove files from the repo with git rm |
dotdotdot list-profiles |
List profiles; --verbose shows each profile's variables |
dotdotdot list-variables |
Show every variable used in templates with set/unset/built-in status |
Sync shortcuts
| Command | Description |
|---|---|
dotdotdot status |
Show sync state and git status |
dotdotdot pull |
git pull |
dotdotdot push |
git push |
dotdotdot fetch [remote] |
git fetch |
Git wrapper
All commands below run in the dotfile repository. Flags and extra arguments are passed directly to git, so every git option works as normal.
| Command | Description |
|---|---|
dotdotdot commit -m "msg" |
Record staged changes |
dotdotdot log [--oneline] [-n 10] [--graph --all] |
Browse commit history |
dotdotdot diff [--staged] [HEAD~1] |
Inspect changes |
dotdotdot stash [push | pop | list | apply | drop | clear | show | branch] |
Stash management |
dotdotdot branch [-a] [-d <name>] |
List, create, or delete branches |
dotdotdot switch <branch> [-c <new-branch>] |
Switch branches |
dotdotdot checkout <branch|file> |
Checkout a branch or restore files |
dotdotdot merge <branch> [--squash] [--abort] |
Merge branches |
dotdotdot rebase <branch> [-i HEAD~3] [--abort] [--continue] |
Rebase |
dotdotdot reset [--soft|--mixed|--hard] [HEAD~1] |
Reset HEAD |
dotdotdot tag [v1.0] [-a v1.0 -m "msg"] [-d old-tag] |
Manage tags |
dotdotdot remote [-v] [add origin <url>] [remove origin] |
Manage remotes |
dotdotdot cherry-pick <commit> |
Apply a commit from another branch |
dotdotdot revert <commit> [--no-commit] |
Create an undo commit |
dotdotdot blame <file> |
Show who last changed each line |
dotdotdot show [<object>] |
Inspect a commit, tag, tree, or blob |
dotdotdot reflog [show|expire|delete] |
Reference log (recover lost commits) |
dotdotdot clean [-n] [-f] [-fd] |
Remove untracked files (-n for dry run) |
dotdotdot bisect <start|good|bad|reset> |
Binary search for a regression |
dotdotdot git <any-git-command> [args…] |
Generic passthrough for anything above |
Global flags
--verbose Print resolved variable table and per-file detail
--quiet Suppress all output
--config Alternate config file path
apply flags
-p, --profile <name> Use a named profile
-o, --override <k=v,...> Override specific variables for this run
Repository layout
Files map directly to paths under $HOME. A file at .config/nvim/init.lua in the repo becomes ~/.config/nvim/init.lua after processing. Two files are never deployed:
dotdotdot-profiles.toml- profile definitions.git/- the git directory