Rust-native dotfiles backup

dotr

Your config is scattered across shell files, app folders, package inventories, and a few absolute paths. `dotr` backs up the selected pieces into Git without taking over how those files are managed.

selective paths Git-backed history explicit restore
$ cargo install dotr-cli
$ dotr init ~/dotbackup --with-defaults --set-default
$ dotr add ~/.zshrc
$ dotr backup --dry-run
$ dotr backup --commit --push
$ dotr restore --dry-run ~/.config/nvim

Why

Dotfiles backup is easy to get almost right.

The annoying part is not copying files. It is deciding what counts as durable config, avoiding noisy app state, and keeping restore boring enough to trust.

Too broad

App folders mix config with churn.

`~/.config`, editor folders, and agent homes often contain logs, caches, databases, generated files, and credentials next to the small files you meant to keep.

Too magical

Dotfile managers can own the shape.

Templates, encoded filenames, and symlink farms are powerful, but they are a lot of system for people who mainly want a private, restorable backup.

Too risky

Restore should never be implied.

`dotr watch` only backs up. Restore is a separate command, previewable by default, and absolute paths require an explicit allow flag.

Common commands

The daily surface area stays small.

$ cargo install dotr-cli
$ dotr init ~/dotbackup --with-defaults --set-default
$ dotr keygen
$ dotr add ~/.config/nvim
$ dotr backup --dry-run
$ dotr backup --commit --push
$ dotr daemon start | status | restart | stop
$ dotr restore --dry-run ~/.zshrc
$ dotr restore --apply ~/.zshrc
$ dotr check

Compact config

Readable enough to audit at 1 a.m.

[[path_set]]
base = "~"
items = [
  ".zshrc",
  ".gitconfig",
  { src = ".config/nvim", include = ["init.lua", "lua/**"] },
  { src = ".hermes", include = ["config.yaml", "skills/**"] },
]

[[custom_backup]]
name = "vscode"
backup = "code --list-extensions > ~/.config/vscode/extensions.txt"
paths = ["~/.config/vscode/extensions.txt"]

Security

Generate keys once, then mark risky paths encrypted.

dotr keygen creates the age identity, writes the public recipients file, and updates dotr.toml. dotr add --encrypt adds one selected path and stores it as an encrypted .age file.

$ cd ~/dotbackup
$ dotr keygen
$ dotr add --encrypt ~/.ssh/config

dotr keygen creates credentials firstly:

~/.config/dotr/identity private key
~/dotbackup/recipients public recipient

then writes this to dotr.toml:

[encryption]
backend = "age"
recipients_file = "recipients"
identity = "~/.config/dotr/identity"

identity is the private key: treat it like a password and keep it out of the backup Git repo. recipients is the public age recipient and is safe to commit.

After that, encrypted path entries stay explicit and auditable:

[[path]]
src = "~/.ssh/config"
encrypt = true

Commit

Recipients are public.

The recipients file can live in the backup repository because it only contains public age recipients.

Keep local

Identity is private.

Keep ~/.config/dotr/identity out of Git and back it up through a separate trusted channel.

History

Encryption is not a scrubber.

If a secret was already committed as plaintext, rotate it or rewrite history before publishing the repository.

Runtime model

Small loop, inspectable artifacts.

01

Select

Use `path_set`, includes, excludes, and custom inventories.

02

Copy

Hash source files, skip volatile state, update `files/`.

03

Index

Write metadata for restore, permissions, and encrypted entries.

04

Commit

Let Git carry history, diffs, remotes, and normal review habits.