Way to store your dotfiles: A bare Git repository (2016)
atlassian.com
atlassian.com
My dotfiles git repo is meant to be cloned in my home directory. It comes with this .gitignore committed in the repo:
/*
!/.vim/
/.vim/.netrwhist
Basically it ignores everything in my home directory, unless I explicitly `git add` it, which matches my workflow. For the few cases where I want to notice changes (like the entire ~/.vim/ subdirectory), I explicitly un-ignore it as you can see above.The only downside I've experienced is my bash PS1 prompt shows the status of the dotfiles repo (branch/dirty/etc) in any directory I'm in that's inside my home dir - I've learnt to ignore it, and it doesn't interfere with CDing into an actual directory that's its own git repo.
In the past I wrote a bash setup script for my dotfiles repository which pretty much does the opposite, symlinking a combination of shared and os-specific directories and files into my home dirs. One definite advantage with your technique is that no special setup script is required. I'm thinking I can obviate the need for splitting ommon and os-specific dirs by just detecting when the OS is Linux or macOS within the scripts themselves and using an if statement to gate their execution or sourcing.
This concept is related to another HN personal favorite of mine: "Best thing in your bash_profile / aliases" [0]. Lots of interesting command-line shell optimization and slick hack ideas in there.
Thanks again for showing me a superior way :)
I did find a pretty neat stack overflow post on the subject of gitignore whitelisting [0]. If we can get to the bottom of possible performance impact, would be cool to add the info there.
$ mkdir foo
$ cd foo
$ mkdir $(seq 1 1000)
$ git init .
$ strace -c git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
75.97 0.058830 57 1035 15 openat
16.48 0.012764 6 2002 getdents
3.87 0.002999 3 1021 close
2.91 0.002254 2 1022 fstat
0.19 0.000147 8 18 14 lstat
0.13 0.000100 4 24 read
0.12 0.000092 6 16 12 stat
0.09 0.000072 3 21 14 access
0.06 0.000048 10 5 write
0.04 0.000033 33 1 unlink
0.03 0.000020 3 8 rt_sigaction
0.02 0.000018 2 12 mprotect
0.02 0.000015 15 1 munmap
0.01 0.000010 3 4 getcwd
0.01 0.000006 0 16 mmap
0.01 0.000006 6 1 ioctl
0.01 0.000005 2 3 brk
0.01 0.000004 4 1 chdir
0.00 0.000003 2 2 getpid
0.00 0.000003 3 1 1 readlink
0.00 0.000002 1 2 rt_sigprocmask
0.00 0.000002 2 1 set_tid_address
0.00 0.000002 2 1 set_robust_list
0.00 0.000002 2 1 prlimit64
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00 0.077437 5221 56 total
Now let's add an ignore-all and check: $ echo "/*" > .gitignore
$ strace -c git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
46.25 0.000568 284 2 getdents
16.78 0.000206 6 35 14 openat
7.41 0.000091 6 16 12 stat
7.17 0.000088 4 21 14 access
5.62 0.000069 3 25 read
4.72 0.000058 3 18 14 lstat
4.15 0.000051 2 22 close
2.93 0.000036 36 1 unlink
2.28 0.000028 1 23 fstat
0.81 0.000010 3 4 getcwd
0.65 0.000008 1 8 rt_sigaction
0.41 0.000005 5 1 ioctl
0.33 0.000004 4 1 chdir
0.24 0.000003 2 2 getpid
0.24 0.000003 3 1 1 readlink
0.00 0.000000 0 5 write
0.00 0.000000 0 16 mmap
0.00 0.000000 0 12 mprotect
0.00 0.000000 0 1 munmap
0.00 0.000000 0 3 brk
0.00 0.000000 0 2 rt_sigprocmask
0.00 0.000000 0 1 execve
0.00 0.000000 0 1 arch_prctl
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 1 set_robust_list
0.00 0.000000 0 1 prlimit64
------ ----------- ----------- --------- --------- ----------------
100.00 0.001228 224 55 totalNormally when I see this I will manually download the bash script, read through exactly what it does, and manually type in each command instead of running the script directly. This way I know what it is doing, and it can't hide command output by piping into /dev/null and doing something without my knowledge.
Seriously people. Never. Trust. Bash Scripts.
https://www.vidarholen.net/contents/blog/?p=746
and seriously just Shellcheck it should be in your repos.
I either want to pull from the standard distribution repositories and rely on things updating automatically, compile from source tarballs with explicit version numbers, or at the very worst have a path-independent binary tarball that can be unpacked anywhere. If you can't manage any of these, then your project simply isn't ready for general availability.
[0] - https://www.idontplaydarts.com/2016/04/detecting-curl-pipe-b...
I suppose the next $2,000 a day consultant will get them the memo.
* initial setup can be tricky even for experienced users * Incorrect use can potentially destroy your home directory
So, I wrote a little utility call "SDF: Sane dotfile manager" that makes the technique used by SneakyCobra approachable to a complete novice and hence more reliable to use.
You can find the introductory text here (https://shreyanshja.in/blog/sane-dotfiles-management/) And the source code here (https://github.com/shreyanshk/sdf)
Let me know how it works for you. :-)
git checkout https://github.com/my/dotfiles.git
cd dotfiles
# this is little more than `find . -maxdepth 1 -exec ln -sf {} ~/ \;`
./install
How are others here managing their dotfiles?EDIT: Also on stow - I tried it a couple times and it never seemed flexible enough. Can it handle things like keeping ~/.config/nvim and ~/.vim in the same stow directory? (And for that matter, can it link them together? I like having the same config for vim and neovim)
Then when you run `stow vim` from the parent of the original 'vim' folder it will symlink everything in there to ~.
You can even have more folders that have a '.config/foobar' inside of them, and when you stow those it will all work itself out nicely!
Edit: You can find examples of this in my dotfiles https://github.com/AnthonyWharton/dotfiles/ (I use stow if you didn't guess :P). For example look at the 'i3' folder and the 'polybar' folder, they both add things to '~/.config '
Although yours is more sound, I'm just moving files to `*.old`.
EDIT: Some quirks to note, especially if you want to steal this script: 1. I use ~/.local/etc as my dotfile directory. 2. I support multiple shells but have all of them use ~/.profile rather than shell-specific files (most config overlaps, and there's a case stmt that deals with per-shell settings). 3. The vim/neovim bit at the bottom will fail silently if the directory already exists; thankfully this is rare, but it should be fixed some time.
I put up my dotfiles here https://github.com/thingfox/dotfiles (sample documentation repository with examples), in it I show how I did the templating for ssh hosts amongst other configs.
This allows me to remove the most sensitive data and use the https://yadm.io/docs/encryption option for that.
The https://yadm.io/docs/bootstrap feature is also awesome as is https://yadm.io/docs/alternates
1. Template files using a syntax that was easy find / replace using a regex. You could use an existing one if you like.
2. Generate a bash install script with all the file variants embedded as base64 strings. I can build this script locally, but I also have a travis ci build that pushes up the install.sh script as a gh-pages like branch.
3. I can now curl the install.sh script from any machine I want and bootstrap my dotfiles. The only install time dependencies are bash, curl, git, base64, mkdir, and echo so it's a very portable self-contained script.
4. During install time, I use a case on hostname to determine which files to use and I use git to put them into my $HOME directory using a similar strategy described by the article.
github: https://github.com/djblue/dotfiles install.sh: https://git.io/vxQ4g
The receiving host runs python, so it can do things like refuse to overwrite files that have been changed locally. I still need to add a notion of hooks to run on the receiver when a given file is changed. If the remote dependency on python/ssh becomes a problem, I will simply add an option to dump a tarball locally.
I really tried to use ansible et al, but those tools seem to be geared towards managing large groups of essentially identical hosts, rather than generally differing hosts with some commonality.
it's great. It has tags to only pull up specific dotfiles (say for emacs, .config etc), and supports configurations for multiple hosts and multiple source folders.
To avoid trashing my home directory, this actually is done to the side and committed into a bare git repository (this part is similar to the article). Afterwards, I use `git --git-dir=... --work-tree=~ checkout -p` to apply any changes one-by-one, allowing me to preserve any local edits I may have made.
All this is available as a sparsely documented Python package: https://github.com/frutiger/stratum
I also keep a barebones “core” of aliases/functions that i use everywhere (e.g. on linux servers as well as my current macos laptop). And then a file that contains the non-core stuff that only get used on my dev environment (MacBook) but not on servers.
—
It’d help if you provided examples of what differences you have between machines though. Most should be pretty simple e.g. slightly different dir structure, different package managers etc.
I have to check out both the main branch and the machine-specific branch in separate directories, and use symlinks. OK by me, though; I don't set up a new workstation often.
try-source () {
if [ -f "$1" ]; then
source "$1"
fi
}
try-source "$HOME/.localrc.$(uname -s)"
try-source "$HOME/.localrc.$HOSTNAME"
try-source "$HOME/.localrc.$(uname -s).$HOSTNAME"I want every dotfile I use to be independent of the rest and a log that shows changes to just that one dotfile, so I store each of them in separate repos and use GNU Stow[1] to manage them.
The above is actually a bit of an oversimplification of what I do, as I store related dotfiles in a single repo as well, so that (for example) all my weechat dotfiles are in a single repo, as I rarely want to checkout a single file independantly of the rest there.
That doesn't really follow; it is easy to check out old versions of a single file with git:
git checkout <revision> <filename>
for ".bashrc from two commits ago on the current branch", that would be: git checkout HEAD~2 .bashrc
Hopefully I'm not just misunderstanding your point here.A Git repo per small text file seems like overkill to me.
Something like:
git checkout c3f2ab -- configs/.vimrc cd ~
git init
echo "*" >.git/info/exclude
Enjoy!You can learn more about how git ignores things here: https://git-scm.com/docs/gitignore
Yadm, a thin wrapper around git allows for alternate files, encryption and templating. See my post https://news.ycombinator.com/item?id=19594859
https://github.com/thingfox/dotfiles
https://yadm.io/docs/encryption
If you rely on your configuration to be secret to be secure it's just security by obscurity and not worth much anyway.
What I had in mind is that the average person wouldn't be a target, but publicly declaring their security vulnerability would attract attacks they wouldn't receive otherwise.
The solution isn’t particularly hard either, simply source a secrets file and make sure you add that to the gitignore file.