lnk moves dotfiles into a Git repo and symlinks them back
🔗 Git-native dotfiles management that doesn't suck.
At a glance
- What is it?
- lnk is a Go dotfiles manager that keeps your configuration files inside a plain Git repository and replaces them with symlinks, with per-host subdirectories, a bootstrap script and a doctor command that repairs broken links. The trade is that it manages whole files, so it has nothing to say about configuration that needs templating or merging.
- Who is it for?
- lnk suits a developer whose dotfiles are already plain files, who wants a Git repository as the only moving part, and who would rather not write a template language to move a vimrc between machines. It does not suit someone whose configuration needs per-host templating or conditional content, and it does not suit a fish shell user, since the README's examples are all Bourne-style shells.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 60 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The model is a file move and a symlink, nothing else
The design choice that defines lnk is stated in the README's own diagram. A dotfile that is a regular file in your home directory becomes a symlink pointing into the Git repository, and the README shows the before and after in two lines. What it does not do is translate. There is no template engine, no per-host variable substitution, and no merge step, because the tagline calls it a Git-native dotfiles manager with no config files, no templates and no ceremony. That is a real constraint rather than a missing feature, and it decides who should use it. If your configuration is a literal file that should be identical on every machine, lnk is a good fit. If your shell profile needs to branch on the hostname, or your git config needs a different user name per account, then the work has to happen somewhere else, and lnk will not do it for you.
The manifest is a plain text file, one path per line
What tracks the managed files is a file called .lnk in the repository root, and it is a plain text file with one path per line and no special format. That is a deliberate contrast with tools that store state in a structured document, and it has a practical consequence: you can read it, edit it, diff it in a pull request, or delete a line to stop managing a file. The same convention extends to per-machine files, where host-specific entries go into a subdirectory named after the hostname with a .lnk suffix, and the README's tree shows the resulting layout with a .lnk file for common entries, a .lnk.work file for work-specific entries, the shared files at the root and a work.lnk directory holding the host-specific storage. The repository root itself is the default location, at ~/.config/lnk, and it can be moved with the LNK_HOME or XDG_CONFIG_HOME environment variables.
The command set is short and each verb does one thing
There are ten commands and they cover the whole workflow. init creates or clones a dotfiles repository, add tracks files by moving them into the repository and symlinking them, rm untracks a file and restores it to its original location, list shows what is tracked, status reports Git sync state, diff shows uncommitted changes, push stages, commits and pushes, pull pulls and restores the symlinks, doctor finds and fixes repository health issues, and bootstrap runs the bootstrap script from the repository. A few of them carry options worth knowing. add accepts multiple files at once, has a --recursive mode that tracks each file in a directory individually, a --host option for per-machine files, and a --dry-run preview. rm has a --force mode that only cleans up tracking when the symlink is already gone, without restoring anything. The global options are a --colors setting, an emoji toggle, and a quiet flag that suppresses output for scripts and leaves you with an exit code.
lnk status # what changed (works even without remote)
lnk diff # uncommitted changes
lnk diff --quiet # exit code only, no output
lnk diff --colors always # force color output (useful in scripts/redirects)
lnk push "updated vim config" # commit & push
lnk pull # pull & restore symlinks
lnk pull --host work # pull host-specific configMulti-host support is a directory and a flag, not a branch
The README's comparison table is explicit about how lnk handles several machines, and it is the feature that separates it from a plain Git dotfiles repo. The --host flag is built in, so a file added with --host work is stored under the work.lnk subdirectory and restored with a matching pull --host work. The table contrasts this with the alternatives and gives the details: chezmoi uses templates, yadm uses manual branches, and stow expects a particular directory structure. For a user with a work laptop and a home machine, that difference is the whole argument, because the alternative to a per-host directory is either a branch per machine or a templating layer. The bootstrap mechanism completes the setup story. Drop a bootstrap.sh file in your dotfiles repository and lnk runs it automatically during lnk init -r, with --no-bootstrap to skip it and a separate bootstrap command to run it by hand.
doctor and the .lnk-backup convention handle a dirty home directory
The failure mode of any symlink-based dotfiles manager is a real file sitting where a link should be, and lnk has a specific answer for it. There is a doctor command with a --dry-run mode that previews the problems it would fix and a real mode that fixes broken symlinks and stale entries. When restoring symlinks, if a real file exists at the target location rather than a symlink, it is renamed to a path with .lnk-backup appended before the symlink is created, so your data is preserved rather than overwritten. The README is clear that this is something to check for afterwards, and that the backups appear after running doctor or pull.
lnk doctor --dry-run # preview issuesThe other removal mode, rm --force, is the opposite case: it is for when the symlink has already gone, for example because you deleted it by hand, and it removes the entry from the manifest and the stored file from the repository without restoring anything to your home directory.
Installing is a script, a cask, a release binary or go install
There are four documented ways in, which is the usual sign of a tool that wants to be used everywhere. The shell installer pipes a script into bash, Homebrew has a cask, release binaries are downloadable, and Go users can install the module directly. The first command in the README is the one that does the most work,
lnk init -r [email protected]:you/dotfiles.git # clone & bootstrap
lnk pull # restore symlinks
lnk add ~/.vimrc ~/.bashrc ~/.gitconfig # track files
lnk add --host work ~/.ssh/config # per-machine config
lnk push "done" # commit & pushsince init with a remote clones the repository and bootstraps it, and the sequence in the quick start is a good mental model of the tool. The Go route is the most reproducible for a developer who already has a Go toolchain, and the repository's own go.mod requires Go 1.25 with cobra for the command line interface and testify for the tests. The build side is a Makefile with targets for build, test, lint, vet and a combined check target, and the release process runs through GoReleaser with a configuration file in the repository root.
What the comparison table claims, and what it leaves out
The README includes a table comparing lnk against chezmoi, yadm and stow across six rows, and the claims are specific. Configuration needed is none for lnk against templates and YAML for chezmoi, Git knowledge for yadm and a Stow directory structure for stow. Multi-host support is a built-in flag, templates, manual branches and manual handling respectively. Bulk operations take multiple files against one at a time for the other three. Bootstrap is automatic on clone, separate scripts, separate, and not available. Health checks exist as the doctor command and nowhere else. The learning curve is given as minutes, hours, medium and low. Those are the author's own numbers, so read them as a position rather than a measurement. The table does not mention the trade-off the other tools win on, which is that lnk has no way to express a value that changes per machine, and the table does not mention that stow's low learning curve comes with a convention you have to learn and respect.
Editorial conclusion
lnk suits a developer whose dotfiles are already plain files, who wants a Git repository as the only moving part, and who would rather not write a template language to move a vimrc between machines. It does not suit someone whose configuration needs per-host templating or conditional content, and it does not suit a fish shell user, since the README's examples are all Bourne-style shells. Before adopting, run lnk doctor --dry-run on a machine with existing dotfiles, note that files are moved rather than copied, and check the last push to the repository, which was on 2026-08-24 with v0.9.1 from 2026-04-25 under an MIT licence.
Frequently asked questions
How do I install lnk?
With `curl -sSL https://raw.githubusercontent.com/yarlson/lnk/main/install.sh | bash`, or `brew install lnk` from Homebrew, or by downloading a binary from the GitHub releases page, or with `go install github.com/yarlson/lnk@latest` if you have a Go toolchain.
How does lnk handle configuration that differs per machine?
Host-specific files go in a subdirectory named after the hostname, added with `lnk add --host work <file>` and restored with `lnk pull --host work`. A plain text .lnk manifest in the repository root tracks the common files, one path per line, and a bootstrap.sh in the repo is run automatically during lnk init unless you pass --no-bootstrap.
What happens if lnk finds a real file where a symlink should be?
When restoring symlinks, a real file at the target location is renamed to a path with .lnk-backup appended before the symlink is created, so nothing is overwritten. The README recommends checking for those backups after running doctor or pull. The separate rm --force mode only cleans up tracking and does not restore anything.
Does lnk need any configuration files or templates?
No. The README describes it as a Git-native dotfiles manager with no config files, no templates and no ceremony. The only state is a plain text .lnk file in the repository root with one tracked path per line, and the repository itself defaults to ~/.config/lnk, overridable with LNK_HOME or XDG_CONFIG_HOME.
How does lnk compare with chezmoi and stow?
The README's table says lnk needs no configuration, supports multiple hosts with a built-in --host flag, takes multiple files in one add, bootstraps automatically on clone and has a doctor command for health checks, where chezmoi needs templates and YAML, yadm needs Git knowledge and manual branches, and stow needs a directory structure. The learning curve is given as minutes for lnk, hours for chezmoi, medium for yadm and low for stow.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/yarlson-lnk)Was this useful?