Using GNU Stow to manage your dotfiles (2012)
brandon.invergo.net
brandon.invergo.net
If you wanted to you could && a few symlinks and other commands into 1 copy / paste'able command to get up and running really quickly. It's also painless to manage secrets by sourcing in optional files. It also works nicely when you want files living across 2 systems (such as WSL 2 and Windows) where on a Windows box you consider certain Windows files dotfiles even though technically they exist elsewhere.
I've been managing my dotfiles like this for a few years and it seems to be working out well not just for myself but others who have want to use them either partially or in full. I open sourced them here: https://github.com/nickjj/dotfiles
I purposely broke up some of the commands because I know that not everyone will want a 100% replica of my dotfiles. Folks copy / paste only the commands they want.
stow links based on the directory structure of the folder you're calling, so I don't have to worry about where things are, just stow fish; stow nvim
There's no symlink script or adapter script to work with stow, everything 'just works' for me.
1. If you want to delete a file, you have to delete it (rm) in one place and delete it another way in another place (git rm).
2. Adding a new file in-situ (in the home directory) requires some finessing to get it back into the git repo and symlinked properly.
3. No easy way to apply a rename operation.
4. After changing the checked out branch, there is no easy way to apply these changes to the home directory.
I use `rm` all the time in git repos... Just `git add` when it is time to stage changes.
That's a feature.
I did not use stow 2.x, but my understanding is that running stow -R will allow you to remove previous dangling symlinks (during the unstow) and add missing symlinks (during the restow). Effectively allowing you to only manage a single state: your git repository.
I also don't have all dotfiles under version control, just the ones I've essentially "built myself", so maybe that makes it easier.
/*
!git/config
!tmux/tmux.conf
I do this and clone my repo directly to .config on a new machine. The only hold out was tmux but that's solved by installing or compiling one of the recent versions released this year (3b/3c). I'll still use manual symlinks for things like bash, but they're outliers.Works great for me.
All you need to do is to keep track of symlinks you've installed with your setup script. This can be done by creating a symlink to the symlink you've installed, which acts as a "GC root." The next time you run the setup script, it would check those "GC roots" to see if they point to a valid file and remove any dangling symlinks.
This is the approach I take for my own dotfiles. I seriously considered using Stow or the bare git approach before, but I decided against it because setting up my dotfiles involved more than just installing symlinks. I had to be able to download files (e.g., vim-plug), clone git repositories, change file permissions, and maintain files that's not meant to be linked into the home directory. I found the flexibility of a custom shell script most fit for my needs.
[1]: The "Deleting Packages" section in https://linux.die.net/man/8/stow
This is exactly what Stow does.
Well, that's what you end up with. Stow just automates the "symlink those files part".
Yes though, use a repo for sync, but stow takes care of at linking your dot files in the right place.
I prefer to skip the symlinks, and just directly use the git repository as my home directory. (I have a sizable .gitignore for things I don't want to track.)
The only drawback to the homedir-as-git-repo approach I've found is that you're always in a git repo, so you have to be careful not adding stuff which was really supposed to go in another repo.
https://www.atlassian.com/git/tutorials/dotfiles
TL;DR:
alias dotfiles='/usr/bin/git --git-dir=$HOME/src/dotfiles --work-tree=$HOME'
dotfiles add
dotfiles commit
...Does anyone know if syncing a git repo over Syncthing (or similar) is a good idea or whether there are likely to be conflicts? I don't know if git names its files such that conflicts aren't possible.
I tend to always run "git status" before adding anything, which makes it fairly obvious what repository I'm in. (Or I'm working via fugitive in vim, which helps in the same way.)
Nothing wrong with tracking dotfiles with Git. It's probably the simplest and easiest approach without requiring any fancy tools other than Git. However, as mentioned in ArchLinux's wiki page, the disadvantage of Git approach is that "host-specific configuration generally requires merging changes into multiple branches."[1]
[1]: https://wiki.archlinux.org/index.php/Dotfiles#Tracking_dotfi...
EDIT: 12+ years at work.
Exactly! I do exactly that. I have an install.sh script that simply iterates over the files and directories in my dotfiles repo and does mkdir and ln
[0] https://github.com/podiki/dot.me/
[1] https://web.archive.org/web/20190924102437/https://expoundit...
[2] https://orgmode.org/manual/Working-with-Source-Code.html
Ansible, for server/OS setup. Things run by init/systemd, involving anything outside of $HOME basically
Backup, using borgmatic. I use this to restore most things in $HOME, except random dotfiles. My documents, my checked out git repos, etc.
Script "setuplets". These I run on demand, to set up my environment piece by piece. Perhaps I do not want to restore my programming environment just because I want to have my custom prompt on a host, for example.
Finding the balance between these have been difficult. What bootstraps what, and especially, how to handle credentials. My backup is encrypted, but how would I make sure I had the keys to restore it? I could restore it from my pass password store, but how would I get the gnupg keys in place first? I have not solved this completely satisfactorily yet
Finally I have a proper term for what I, too, have been doing all these years! :-) It's indeed the best-possible approach I've found, though there are a number of things that I haven't yet solved for myself in a satisfactory manner:
- With shell scripts there are no idempotency guarantees and there is no easy undoing / uninstalling / clean-up, especially after updating a setuplet.
- With shell scripts everything is defined imperatively as opposed to declaratively. In particular, setuplets usually operate on the filesystem directly and testing and dry runs are almost impossible.
- No status report as to what a setuplet wants to set up (software, configs, cronjobs…) and what is already set up on the current machine. That is, no diffs. This makes sharing setuplets and configs between multiple machines (say, personal and work laptop) rather cumbersome. For instance, I might forget to re-execute a setuplet on the second machine which could then lead to a missing software dependency or a mismatch between config and software.
- No simple, out-of-the-box way to have different configs/dotfiles for different machines, in particular: no config templating.
- It's hard to share common settings across applications without duplicating them everywhere. For instance, I would like to define a common set of colors / a common theme for my window manager, my terminal, my editor and so on. Similarly, (some) keybindings should be the same across applications. Moreover, I have a set of common directories in my home dir (for binaries, logs, cache etc.) that all my setuplets & dotfiles should use.
- Dependencies and interactions between setuplets are often implicit. They interact with and depend on one another through a myriad of ways, like software dependencies (of course) but also PATH modifications, cronjobs, bash aliases, file system modifications … These are very hard to recognize and, even worse, to refactor.
- Bash scripts are error-prone and cumbersome to write and debug (and refactor).
- My setuplets don't have a common command line interface and their relation to one another is unclear. (In which order should they get executed?) I tend to write scripts that invoke all the setuplets in the right order but it still seems messy and error-prone.
I've tried solutions like Ansible but I've found that its purely declarative DSL is not flexible enough to cover all my use cases in an elegant manner.
…which is why I'm currently working on a small Python library that will hopefully solve or at least ease the above pain points for me. Once the library is finished, I will rewrite my setuplets in Python (using the library to do the hard and tedious work), so that I end up with one single Python project of dotfiles and setuplets (exposed through one single command line tool) that, once executed, will automatically set up an entire machine for me within a few minutes. One nice thing would be that all inter-setuplet dependencies would be expressed through Python code (with proper typing, encapsulation in modules and everything) which could then easily be explored (and also refactored) with an IDE. Sure, this sounds like a lot of work but given that I intend to use my dotfiles for a couple more decades, it seems well worth it.
Of course, whether my approach will ultimately be able to solve all the challenges above remains to be seen but I'm at a point right now where I'm convinced that proper software engineering methods (especially dependency injection, type checks, tests etc.) would be a real boon for managing my (hundreds of) dotfiles.
Bonus: now even configuration files can be linked to the software that needs it; when you remove a software you also remove all the files related to it and there are no leftovers.
I also use GNU stow in my dotfiles[1] especially so that my setup still works on systems without Nix installed.
1. Bare git repo in your home directory ($HOME/.files)
2. Alias for prefixing git commands ("env GIT_WORK_TREE=$HOME GIT_DIR=$HOME/.files")
3. Strict .gitignore file (that ignores all files by default)
Simple to add files: `h git add .vimrc`
Have this set up for myself. Works great https://github.com/tmm/dotfiles
I explain here the steps of how I do it: https://github.com/josepmdc/dotfiles#2-if-you-want-to-manage...
Basically adding a alias to my zshrc, that runs: `git --git-dir=$HOME/dotfiles --work-tree=$HOME`.
And then set status.showUntrackedFiles to no.
See: https://github.com/sp1ritCS/dotfiles
I've got this idea from the following blogpost: https://news.opensuse.org/2020/03/27/Manage-dotfiles-with-Gi...
Works quite well ... but I'm really, really ready for a rebuild of how Linux systems are built. I hope systemd-homed will deliver.
Imagine you could ssh into a machine with a flag that forwards your local config. Or even your local bin.
It's a bit strange that now it's a Cloudflare trademark.
I would not wish this pain on others. It works great when your systems are homogenous. It rapidly becomes a pain when you need to make customizations that only apply in a particular network, or on hosts with a particular OS version, etc.
My bashrc has basically just become a monstrosity to control what files get sourced for this particular host. Answering why a particular env car is set to what it is ends with me trying to trace what files get loaded, what they set, and the order they get loaded in.
Much more painful than separating the concerns and having Ansible template out my bashrc.
At heart it's a short snippet that just checks for existence and sources each file in the directory:
https://github.com/targaryen/bashrcd/blob/master/install/ins...
I added aliases to list/edit/remove entries from the .bashrcd directory and resource it. And a script I can call with a one-liner to edit bashrc on a new machine to add the sourcing and the helper aliases.
It'll load alphabetically so I can prefix entries with a number to specify load order (defaulting to 0100 so I don't need to specify this in the commands unless I explicitly changed them).
So the end result is that I can quickly edit or create a new bashrc entry by running 'ebrc entryname'. This opens ~/.bashrcd/0100--entryname in vi, and when it's saved it'll re-source so the add/change takes effect immediately.
Or 'lbrc' to list contents of the directory, or 'rbrc entryname' to remove ~/.bashrcd/0100--entryname
It's fairly simplistic but takes away most of the cognitive load of managing a complex bashrc.
I feel obligated to share my Bash script, dotfiles.sh[1], that accomplishes what Stow does, but with a few tweaks that I found particularly useful:
dotfiles.sh targets the user's home directory by default (i.e. stow -t $HOME).
dotfiles.sh never symlinks directories, only files (i.e. stow --no-folding). (This was the straw that broke the camel's back and made me roll my own script in the first place.)
dotfiles.sh makes backups of local config files and can restore them if you remove your symlinked version.
My script is quite old now, and I use it so seldomly I'm not convinced there aren't bugs. YMMV.
Recently I’ve moved to NixOS and have been considering trying out Nix’s home-manager (mostly just out of curiosity - I’m perfectly happy with stow). If anyone has tried both stow and home-manager, I’d be interested to know your thoughts on how they compare.
I'm not sure if it's all worth it. I recently got a new machine, and it sure reduced the pain of setting it up. But I get a new laptop every four years or so. Bike shedding is fun.
I have a strict no secrets policy and they get laid down manually on any system that needs them.
`yay -Qe | cut -f 1 -d " " > .config/packages.txt` to back up my packages (without version), push to remote, and then `cd ~ && yadm clone -f <remote> && yay -S $(cat .config/packages.txt)` to reinstall everything. Piece of cake.
The only thing I am missing is tracking system files, like /etc/pacman.conf, and it looks like there's a way to do it with yadm, but it looks a bit clunky and I haven't tried.
[0] https://yadm.io/
So what I do on any new system is just:
git clone https://github.com/susam/dotfiles.git
cd dotfiles
./setup
And if I want to undo all the setup for some reason: ./setup rmSo I added a note to myself into my .bashrc about this command, and patted myself on the back. The next time I reinstalled Linux, I couldn't remember the name, but I remembered that I wrote a note to myself about it. So opened up my .bashrc, and... I didn't feel so smart.
cd ~
git init
git remote add origin [URL]
git fetch
git checkout -f master
git clone --bare <dotifles repo> ~/.dotfiles
git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME checkout
git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME config --local status.showUntrackedFiles no
And then I add an alias: alias cfg='git --git-dir=$HOME/.dotfiles --work-tree=$HOME'
Now I can just manage my dotfiles as a git repo, and I don't have to worry about accidentally adding local secrets to the repo.> all your dotfiles are now neatly organised
isn't that what .config is for?
> install locally built packages in /usr/local/stow/PKGNAME-VERSION [...] so you don’t have to worry about any stray files
Isn't that what /opt is for?
> (HN comments live-linking to a repository)
So maintain externally and export as needed.
... am I missing some really important context here? I downloaded stow to check it out, but the whole 'dotfile/installation' use-case in the article sounds like a nothingburger to me.
[1] https://gist.github.com/XVilka/f124936a336a5a43f54915369719e...
I ended up switching to https://www.chezmoi.io/, after learning about it here on HN.
Its a small, statically linked binary which is easy to install and the templating feature is really useful.
Obviously the Windows registry works terribly in practice, but the idea of a centralised store seems to be coming back into fashion.
Environment variables and their defaults:
ZPKG_SRC = ~/.local/pkg
ZPKG_DST = ~/.local
ZPKG_DB = ~/.local/pkg/.db
It means that if you install anything from scratch, you have to `make install` (or the like, depending on the build system) it to, say, `~/.local/pkg/foo-1.0` and then run `zpkg link foo:1.0` to install (i.e. link) the "package".After that you just have to make sure you have (typically in your `~/.bash_profile` file):
- added `~/.local/bin` to your `PATH` environment variable, and
- added `~/.local/man` to your `MANPATH` environment variable
Seems to do the job. For help, read the source code or type `zpkg --help` which should be of tremendous help.
By the way, I have noticed that someone created a package manager with the same name, but its initial commit was in 2019, while this one's was in 2017.
It is written in OCaml, so you do need to have the OCaml compiler installed. I recommend doing it via `opam`, but your Linux distribution's package manager will suffice (simply `ocaml` on Arch Linux, for example). Run `make` to compile it, it will produce a working executable file.
Direct link to the source code: https://zolk3ri.name/cgit/zpkg/tree/src/zpkg.ml
If you find any bugs, report it via e-mail which can be found in the `LICENSE` file. I reported a bug before and it was fixed almost immediately. I suppose you can send pull requests or mention missing features, the creator seemed friendly to me.
---
The above is a modified version of an old comment of mine: https://news.ycombinator.com/item?id=24238587
---
To be frank, I forgot how it compares to GNU Stow because it was many years ago, but I did use GNU Stow prior to finding this program. All I remember is that it is way simpler, and it seemed to be perfect for my use case, no more and no less than what I needed. Maybe it works for you, too.