Move dotfiles to ~/.config/lnk, get symlinks back, use Git normally. Single binary, no config files, no fluff. Built because chezmoi was too complex and plain Git was too manual.
When you ‘lnk add’ a file, check it for patterns that might indicate it contains a secret, and warn the user if indications of a secret are found. Require —force or something to override.
There are so many secrets spread across dot files. Is it possible to encrypt and store them in remote and de-encrypt when it’s pulled to local machines?
That moves the problem to "how do we securely manage shared keys."
And it adds "how safe are those encrypted secrets [edit: changed from "keys" to more general language] that are committed?" and "what about previous revisions . . . because it's version control?" and "are we sure we're managing offboarding securely?"
There are probably other concerns but those are the ones the immediately shout at me.
The freedesktop secrets service was meant to do provide a central secrets storage location and API for access on Linux, but few command-line tools use it.
I take care to wrap my commands in functions that export only for that scope. If you have exported variables in your bashrc it'll be shared with everything you spawn through your shell, including evil NPM packages.
I avoid putting secrets into dotfiles and try to avoid putting non-secret, personal references in dotfiles. If I really have to, I would start with making the repository private.
Great question! GNU Stow and lnk solve similar problems but with different approaches:
GNU Stow:
- Package-based structure: Requires organizing dotfiles into "packages" (subdirectories)
- Symlink-only: Just handles symlinking, no version control integration
- Manual Git: You manage Git separately (git clone → stow package-name)
- Perl dependency: Requires Perl to be installed
- No safety net: No atomic operations or rollback if something goes wrong
lnk:
- File-based workflow: Takes your existing dotfiles as-is, moves them to managed location
- Git-integrated: Wraps Git commands (lnk push, lnk pull) for seamless workflow
- Atomic operations: If something fails, automatically rolls back changes
- Single binary: No dependencies, just download and run
- New machine workflow: lnk init -r repo && lnk pull handles clone + restore in one step
Key Difference in Workflow:
Stow approach:
# Organize files into packages first
mkdir -p ~/.dotfiles/vim/.config/nvim
mv ~/.config/nvim/init.lua ~/.dotfiles/vim/.config/nvim/
git add . && git commit
# On new machine:
git clone repo ~/.dotfiles
stow vim # Creates symlinks
lnk approach:
# Start with files where they are
lnk add ~/.config/nvim/init.lua # Moves and links automatically
lnk push "added nvim config"
# On new machine:
lnk init -r repo && lnk pull # Clone + restore in one command
Bottom line: Stow is a pure symlinking tool that you combine with Git manually. lnk is an opinionated workflow that handles the entire dotfiles lifecycle (move → version → sync → restore).
My main gripe with dotfile managers (including lnk) is that they assume a uniform environment. I haven't found one that doesn't make this fundamendal assumption.
Some scenarios where dot fils may differ between computers:
- My .gitconfig is different on my work laptop than my desktop.
- I don't have neovim installed on my pi zero running DNS for my home network.
- My zsh functions for making animated gifs won't work if specific tools are not installed.
- An alias to open an image with the default image viewer is different between macos and linux.
- I only have rust toolchain installed on my home desktop, so I shouldn't see it in my PATH on my work laptop.
Is there any solution out there that can handle similar cases? Or are these requirements unique to me? (I don't quite believe they are.)
This is my answer too. I extract the stuff that is unique from my config files to files sourced by my configs and then make it unique by hostname with yadm. It works so well
I handled this by just writing a simple install script that symlinks different files depending the OS (but could use any criteria—hostname, installed packages, etc). Updating the dot files is just a matter of a git pull. If I’ve added a new dotfile, then I run the install script again.
Git supports conditional configuration so you can tell it to load different config files based on file system location or what have you. I use it to swap signing keys and author info depending on whether the repo is personal or for work based.
I solve this locally by having a bash_profile that is universal and then OS-specific files that get loaded by a function that checks the OS. It’s slightly more to manage, but works perfectly. Completely compatible with any dotfiles management tool I’ve encountered. No reason this couldn’t be expanded to factor additional variables.
I do the git branch thing with GNU stow and it absolutely sucks, but it's the only thing that sucks. This seems to solve different problems that I don't have but not the one that I do. I've been meaning to try chezmoi specifically because managing machine-specific branches sucks.
I switched over to chezmoi a few years ago - previously had been managing a "main" (macOS) and "Ubuntu" branch for dots. turned out to be a huge pain especially once I ran into work-specific config, or wanted to try other Linux flavors. figuring out the templating and ignore rules to [merge everything](https://github.com/Amar1729/dotfiles/commit/00177e45b09bba80...) took about a weekend's effort but it was so worth it
I have a Makefile that runs certain stow commands depending on the hostname of whatever machine I'm on. I keep machine specific files in their own directory, and I also have a shared directory for shared dotfiles. It works well for my needs.
Chezmoi seems to "do it all" but it also seems overly large and relatively complex. I'm just a bit afraid I'll have to read through the documentation over and over again, especially if it's been a while.
Another approach that avoids symlinks and avoids a git repo in $HOME (i.e. everything is a subdir of that git repo) is to use the option "git-dir" to clone into a subdir, e.g. `$HOME/.dotfiles/` but checkout into $HOME:
git clone --bare [email protected]:.../dotfiles.git $HOME/.dotfiles
git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME checkout
alias dotfiles='git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'
dotfiles config --local status.showUntrackedFiles no
Now the "dotfiles"-alias can be used like git, "dotfiles add, checkout, log", no symlinks are needed and you avoid "contaminating" subdirs of $HOME (git searches parent dirs for ".git", so a ".git" folder in $HOME kinda sucks)
I have been using the "bare git at $HOMEDIR"[0] approach for several years.
Benefits:
1. no extra tools.
2. one off task for setting up the git repo and alias `dotfiles='/usr/bin/git --git-dir=${HOME}/.config/dotfiles --work-tree=${HOME}'`
3. all files are where they are, no symlinks, copies, etc.
Caveats:
1. $HOME/.gitignore just ignores everything because it contains a single "*" char[1]. So adding new files must be done with dotfiles add -f ~/.newfile to track.
I like this a lot. The caveat is not that bad because anyway with stow or other solutions you still need to add things manually.
I believe one could even add stuff like `!.bashrc` to the gitignore so it can keep track of these files automatically. Hence we could have template .gitignore for most common use cases.
I don’t do the * in the gitignore because I use the confit alias and disable showing untracked files. That way I can use the .gitignore in the home as my global gitignore
I love the bare repo method though. I’ve been using it for several years and haven’t had a need for another
Is that an automatic/default setting or did you just pick that path and you have to set the ignore filename in .gitconfig? Because, I know I could name it ~/.gitignore_global too.
that's pretty cool. I didn't realize that. when I first started that wasn't how it was being used so I never picked up on it. Using .config/git/ saves me a couple of lines in the config file.
The only thing I don't like about stow is that I can't move files around easily. If I decide to move some files around within modules, I have to unstow, then move them, the stow again. If I forget then it leaves dangling symlinks hanging around.
One big benefit of symlinks (really, "not storing it in the deployment") is that my Git repo doesn't have a bunch of hidden files in it because they can appear in the link's path rather than the repo's path. I can also split up files based on "why it exists" rather than "where it lives". For example, I can have the "enable Rust support" group of configurations:
- add Rust things to the list of packages to install
- add any Rust-specific configurations for Neovim and `zsh`
- bring in any Rust-oriented Neovim plugins
These files all then live next to each other rather than being scatter-shot between Neovim, zsh, and some giant list of packages. Additionally, if I later decide to disable "Rust support" on a machine, the broken symlinks in `$HOME` let me clean up easily without having to actually excise things from the repository.
That said, I have my own system that I built up years ago and it's never been abstracted out for anyone else to use so of course it's going to fit my needs better than anything else.
Interesting; these methods seem to follow the same pattern. They both track specific files in a controlled bare git repo, and both transparently link a $HOME file into that repo. Differences are the git method “links” - within the work tree - while ‘lnk’ creates a filesystem link. They both require a special command - a shell alias, vs an installed binary, and both indirectly leverage git subcommands (via the shell alias or the binary).
An advantage of the git method is it doesn’t touch $HOME, and so if the git repo gets wiped out there’s no harm. Am I missing anything?
This looks fine to use, but considering you can a achieve the same with a git repo and a 20 line bash script to make the symlinks[0], it seems to me precisely the kind of thing beginners should learn to do themselves rather than trust the magic and assume there’s something complicated going on they couldn’t understand. The fact that all of the commands are the same as the underlying git commands is probably better than the alternative (a bunch of random stuff) but it also makes clear you could do the same with git directly.
When you ‘lnk add’ a file, check it for patterns that might indicate it contains a secret, and warn the user if indications of a secret are found. Require —force or something to override.