a simple dotfiles manager.
go install github.com/olimci/tohru# show the banner and top-level help
tohru
# print the current version
tohru version
# install application files (optionally load a profile immediately)
tohru install [--no-hooks] [profile]
# list cached profile slugs and paths
tohru profile list
# create a new empty profile in ~/.tohru/profiles/<slug>
tohru profile new <slug>
# copy a local path into a profile and add manifest entries
tohru profile add <slug> <path>
# merge nested roots in a profile manifest
tohru profile tidy <slug>
# load some dotfiles (path, or a cached profile slug)
tohru load [--no-hooks] [profile]
# preview what loading a profile would do
tohru plan <profile>
# reload current profile
tohru reload [--no-hooks]
# unload current profile
tohru unload [--no-hooks]
# uninstall tohru
tohru uninstall [--no-hooks]
# see what files are being tracked by tohru
tohru status
# inspect or manage trusted hooks
tohru hooks list <profile>
tohru hooks trusted [profile]
tohru hooks allow <profile> <hook-id>tohru will automatically take backups of files that might be clobbered, and will automatically restore them once the conflicting profile is unloaded. this behaviour is configurable in config.
dotfiles are defined with a tohru.json file:
{
"schema": 1,
"requires": {
"tohru": "0.2.0"
},
"profile": {
"slug": "my-dotfiles",
"name": "my-dotfiles",
"description": "personal setup"
},
"roots": [
{
"source": "home",
"dest": "~",
"defaults": {
"type": "link"
},
"tree": {
".zshrc": ["copy"],
".config": {
"kitty": {
"kitty.conf": [],
"theme.conf": [],
"kitty.app.png": ["copy", "untracked"]
},
"nvim": {
"after": {
".": ["untracked"]
}
}
}
}
}
]
}In the structural tree format, arrays represent files and objects represent directories. Directory metadata uses the reserved "." key, and an empty array [] means “inherit defaults with no overrides”.
In profile source trees, hidden path segments are encoded with a dot_ prefix, so .config/nvim is stored as dot_config/nvim.
When a loaded profile has profile.slug, tohru caches slug -> profile path in state, so future tohru load <slug> works without the full path.
Profiles can define trusted post-operation hooks:
{
"hooks": [
{
"triggers": ["post_load", "post_reload"],
"run": ["kitty", "@", "load-config"],
"cwd": "home"
}
]
}Supported events are post_load, post_reload, and post_unload.
Hooks are not trusted automatically. Approvals live in the local tohru store, not in the profile manifest:
tohru hooks list my-dotfiles
tohru hooks allow my-dotfiles <hook-id>
tohru hooks revoke my-dotfiles <hook-id>When an untrusted hook prompts interactively, choose run once, skip, or trust and run. Trusted hooks run without prompting.
For safety, tohru rejects manifests that try to write into the store root or back into the active profile source tree.
You can also add machine-local protected destinations in ~/.tohru/config.json:
{
"schema": 1,
"options": {
"protected_paths": ["~/.ssh", "~/.gnupg"]
}
}Configured protected paths are checked the same way as built-in protections, including paths reached through existing parent symlinks.