Keep your Linux config files in one place — and keep them in sync.
stitch takes the scattered files in your home directory (~/.bashrc, ~/.config/nvim, ~/.gitconfig…) and links them back to a single folder you control (like ~/dots). Edit the file in your home dir, and you're really editing the file in your repo. No copying, no re-adding, no drift.
If you've ever wanted to back up your dotfiles, move them to a new machine, or just stop losing them — this is the tool.
New here? Dotfiles are just hidden config files — the ones starting with
.in your home folder. Stitch puts them in a Git repo so you can track and restore them.
Linux only. Built on standard Linux links. Not tested on macOS, doesn't run on Windows.
No Rust needed. Go to the Releases page and download the file for your computer.
- Intel/AMD PC or laptop →
stitch-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz - Raspberry Pi / ARM server →
stitch-vX.Y.Z-aarch64-unknown-linux-gnu.tar.gz
Tip: Not sure which you need? Run
uname -m— it printsx86_64for most desktops,aarch64for ARM.
Then install it:
# example for x86_64 — swap the filename if you grabbed the ARM one
# (check the Releases page for the latest version number)
curl -LO https://github.com/bermudi/stitch/releases/download/vX.Y.Z/stitch-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz
tar xzf stitch-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz
sudo mv stitch /usr/local/bin/
stitch --helpEach download has a .sha256 file next to it if you want to verify it. You can always find the newest version at https://github.com/bermudi/stitch/releases/latest.
Once installed, stitch can update itself:
stitch self-update --check
stitch self-updateIf the binary is in a root-owned directory such as /usr/local/bin, run the
second command with the same permissions you used to install it. The updater
checks the published SHA-256 and replaces the executable atomically. The
checksum detects corruption; release authenticity relies on HTTPS and GitHub.
Need Rust first (2024 edition). Then:
cargo install --path .
# or, to try it without installing:
cargo build --release
./target/release/stitch --help# 1. Make a folder for your dotfiles
mkdir ~/dots && cd ~/dots
# 2. Set it up — creates two small config files (see "How it works" below)
stitch init
# 3. Add something you already have — say your Neovim config
# This moves ~/.config/nvim INTO ~/dots and leaves a link behind
stitch add ~/.config/nvim
# 4. Or create a new empty file-backed store for a file that doesn't exist yet
stitch add ~/.bashrc --file
# 5. Put the links in place (and re-run this any time you change config)
stitch apply
# 6. Check that everything looks good
stitch statusWhat just happened?
Before: ~/.config/nvim (real folder on disk)
After: ~/.config/nvim → ~/dots/nvim (a link pointing back to your repo)
You edit ~/.config/nvim/init.lua like normal — your editor follows the link and saves straight into ~/dots/nvim/init.lua. Commit and push ~/dots and your setup is backed up.
To undo one thing: stitch remove nvim removes the links and the bookkeeping, but leaves the files in your repo alone. remove never deletes the store directory in the repo, so to re-add the same store you either keep the old directory (rename it first, then add the original path) or remove it yourself (rm -rf ~/dots/nvim) before running add again — add refuses to adopt into an existing store directory.
- Store = a folder in your repo, like
~/dots/nvimor~/dots/shells. One unit of stuff to manage. - Target = where it should appear in your home, like
~/.config/nvimor~. - Link = stitch makes the target point back at the store. Edits at the target land in the repo — no second step.
Stitch keeps two config files so your comments never get wiped out:
stitch.toml(in your repo root) — yours. You write it, you keep the comments. Things likewhen(only on certain machines) andhooks. Stitch creates it once atinitand then never touches it again..stitch/state.toml(hidden folder) — stitch's. The list of what goes where (target,files).addandremoveupdate this one.
That's it. Change the config, run stitch apply — stitch makes the filesystem match.
Example of the two files
stitch.toml — your choices:
[vars]
editor = "nvim"
[stores.shells.when]
os = "linux"
[stores.git]
hooks = { post = "git config --global core.editor nvim" }.stitch/state.toml — stitch's inventory:
[stores.nvim]
target = "~/.config/nvim"
[stores.shells]
target = "~"
files = [".bashrc", ".zshrc"]
[stores.git]
target = "~/.config/git"Everyday:
| Command | What it does |
|---|---|
stitch add <path> |
Bring a file/folder into the repo and link it back. Use --file for a missing single file. Use --to <store> to add an existing file to a grouped store. Existing repo paths and unsafe target/destination parents are rejected; --dry-run checks them without changing anything. |
stitch apply |
Make your home match the config. Creates/fixes links, reports anything in the way. |
stitch status |
Is everything linked? Shows linked, missing, conflict, or broken. |
stitch diff |
Preview what apply would do, without changing anything. Add --exit-code for a scriptable exact-state check. |
Occasionally:
| Command | What it does |
|---|---|
stitch list |
Show every store and where it points. |
stitch remove <name> |
Unlink and forget a store (your files stay in the repo). |
stitch edit |
Open stitch.toml in your editor. stitch edit nvim opens that store's file. |
stitch doctor |
Health check — missing folders, broken links, leftover settings. |
stitch prune |
List links pointing into your repo that nothing uses anymore. Add --yes to actually remove them. |
stitch import |
Found old hand-made links pointing into your repo? Register them. |
stitch plan |
Save exactly what apply would do to a file, so you can review and run apply --plan <file> later. |
For agents & scripts:
| Command | What it does |
|---|---|
stitch explain |
Show the resolved desired state for this host — every store, target, and entry with when clauses evaluated. One call instead of parsing stitch.toml + state.toml yourself. --active-only filters to stores that apply here. |
stitch why <target> |
Investigate one path: what store owns it, its source, link state, and owning config. One call instead of cross-referencing list + status + doctor. Handles paths inside a whole-dir store (matched_subpath) and skipped-platform stores. |
stitch log |
Show the audit log of mutating operations (.stitch/log.jsonl). --limit N reads the tail. Verify what actually happened without re-running diff. |
stitch schema |
Emit the canonical JSON schema for the --json interface — every command's data shape, the error shape, recoverable_via kinds, and exit codes. Self-describing; no repo required. |
apply, add, and remove support --dry-run (just pretend); diff is always a preview. apply and diff take --only <name> to act on one store, and apply takes --force to back up a real file to *.bak before replacing it. add takes multiple paths at once for bulk mode (simple stores only, no per-path flags).
Add --json to any read/plan command, or to supported dry-run/reporting commands such as add --dry-run, for machine-readable output. Every --json response is one envelope: {schema, command, ok, warnings, data, error}. On error, error.recoverable_via lists machine-readable next steps (force-apply, adopt, edit-template, manual, …) so a script can branch without parsing prose. apply --json returns a composite envelope — desired (what the world should look like), plan (what it'll do), result (per-op outcome), and post_status (verification after execution) — so a single call is the full agent loop. Run stitch schema --json for the canonical shape of every command.
Want the full details? See SPEC.md — every flag, edge case, and design choice is there.
Need a config that changes per machine? Rename a file to end in .tmpl and use {{ }} inside:
# git/gitconfig.tmpl
[user]
name = {{ hostname }}
email = {{ vars.email }}
[core]
editor = {{ env("EDITOR", "nvim") }}
On apply, stitch renders it to .stitch/render/ (locked down, not tracked by Git) and links the target without the .tmpl ending. Edit the .tmpl source, not the rendered file.
Available inside templates: {{ env("VAR") }}, {{ vars.key }}, {{ hostname }}, {{ os }}, {{ arch }}, {{ distro }}, {{ shell }}.
See SPEC.md §Templates for the full list.
Stitch changes files in $HOME, so it's careful:
- Never uploads anything. Backups stay on your disk.
- Never quietly replaces a link it didn't make. If a link points at stow, chezmoi, Nix, etc., stitch calls it a conflict and stops — even with
--force. --forcebacks up, not deletes. A real file in the way gets renamed tofile.baknext to the original. If.bakalready exists, it stops rather than pile up.- Won't follow trick paths. Entries with
/at the start or..inside are rejected — nothing escapes your store or target. pruneonly lists by default. It shows orphaned links; it only deletes with--yes.
- SPEC.md — the full contract: every command, config field, hook, and conflict rule.
- CHANGELOG.md — what's new in each release (including the trust-review fixes).
- Releases — download binaries and read release notes.
- AGENTS.md — contributor notes (architecture, safety rules).
- docs/reviews/2026-06-15-trust-review.md — the deep safety review this project was built around.
cargo build
cargo test # unit + CLI integration tests
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warningsA change only counts as done when cargo fmt and cargo clippy -D warnings are clean.
Personal-use project. See repository for license terms.