64 comments

[ 4.4 ms ] story [ 139 ms ] thread
[This] way (linked from the tutorials section of the page) is a great way to version your dotfiles without symlinks or installer scripts.

[This]: https://www.atlassian.com/git/tutorials/dotfiles

What issue do symlinks have that makes them worth listing as a negative? Asking as someone who wrote an installer script to make symlinks a decade ago, and has used it whenever setting up a new computer ever since.
I use them and have no issues, but some editors don’t respect symlinks when making backups when you’ve opened the symlink (Rename existing file, make new file with the old name, and now the symlink is renamed and subsequent updates are unlinked as well). I seem to recall I had to set an emacs var to avoid this behavior.
Flatpaks can be an issue depending on the sandboxing. The application may be restricted from accessing the target. Using a hardlinks works, so it isn't that big of an issue to have a couple hardlinks, but it is still something to keep in mind.
Enabling/disabling systemd services is done via managing symlinks. Unfortunately, when disabling a service in systemd, it follows and remove symlinks which can remove your symlinked service files.
I use this method, it's great. WAY superior to and WAY easier than using symlinks and having to constantly copy/move and symlink things to a git repo.

I use this script (which I modified a bit from the Atlassian one) to bootstrap it on a new $HOME dir (replace "***YOUR_GIT_REPO_ADDRESS***" with your address; also, you need to copy the function ,cfg into your .*rc file for command-line use):

    #!/bin/bash
    set -euo pipefail # the usual stuff

    # TODO: copy the below function into your .*rc file
    function ,cfg {
        /usr/bin/git --git-dir="$HOME/.cfg/" --work-tree="$HOME" "$@"
    }

    # TODO: replace '***YOUR_GIT_REPO_ADDRESS***' below
    git clone --bare git@***YOUR_GIT_REPO_ADDRESS***/.cfg "$HOME/.cfg"
    ,cfg config status.showUntrackedFiles no

    if ,cfg checkout; then
        # worked fine, we're done
        echo "Checked out config."
    else
        # it failed, so assume we need to move some files out of the way
        echo "Backing up pre-existing dot files."
        mkdir -p "$HOME/.config-backup"
        ,cfg checkout 2>&1 | grep -e "\s+\." | awk "{'print $1'}" | xargs -I{} mv {} "$HOME/.config-backup"/{}
        ,cfg checkout || (echo "Still cannot checkout configs; see messages and fix." && exit 1)
        echo "Checked out config."
    fi
I also have a git alias to commit files that are tracked with a message:

    ctm = commit -a -u -m           #commit all tracked files with message
So you can do like this to commit changes and push them:

    ,cfg ctm "I modified my .zshrc"
    ,cfg push*
I love the atlassian method for dotfiles. I have been using for a few years now and currently stored on my private gitea server. I also create a branch for each variation, linux, macOS, macOS arm, windows. Basically they’re the same but they all have slightly different things I cannot use universally.
I had different branches for different machines too in the past but it felt very inefficient to me. The configuration was the same for 99% and for the last 1% I had to manually copy stuff back and forth. At the moment, I just try to use tools that allow me to have different configurations for different hostnames or OSs but maybe I should look into one of the fancier dotfile managers…
FWIW, that isn't the "atlassian method" for dotfiles. People have been doing bare repos for a while.

Also, pretty sure they even say at the start of the post they got the original alias from a HN commenter.

tl;dr: Create a .gitignore in your ~ with an asterisk on the first line and `!.dotfilename` on subsequent lines; the asterisk means Git will ignore everything, and the ! means Git will un-ignore that specific file or directory. (Be sure to include the .gitignore file itself.) Use Git as normal and push. To deploy on another machine/account, the easiest method is to check out as normal into a temporary directory, then move the .git directory from that temporary directory into your ~ and run `git revert`. Git will see all of the missing dotfiles as changes to be reverted and revert them into existence.
Wow, thank you! This is pretty easy and without all the acrobatics you usually see people do when dealing with dotfiles.
One potential drawback of doing that is that every directory under ~/ that is not itself part of another git repository becomes part of that git repository, which could make some programs act weird, particularly project auto-detectors in IDEs and such.

To avoid that, one can rename the `.git` folder, effectively disabling auto-discovery, and run git with the option `--git-dir=<newname>`.

So the IDE or whatever would basically traverse up the filesystem looking for the first directory with a .git subdirectory? I've never encountered something like that but I suppose it's possible. Do you know of any IDEs specifically that do that?

At any rate, I didn't know renaming the .git directory was possible. Do you have to add the --git-dir option to every subsequent git command? Sounds like a pain but my dotfiles change rarely enough that I guess I could live with it if I had to.

you can also create a .git file with the path for the git-dir folder (git-dir: /path-to-git-folder/) but that is kind of like having the .git folder with the exception that some programs don't seem to support it.
Yeah, the git command traverses up the filesystem until it finds a ./.git/HEAD file or until it reaches /, whatever happens first. The problem comes when a 3rd program uses git to find the root of a project that doesn't exist and instead of failing finds ~/.git and naively decides to index everything under ~/. I don't know of any specific IDEs that do that, although I vaguely remember running into a similar issue in the past, maybe not with an IDE, which is probably why I do it that way.
In `~/.git/config` do:

   [status]
     showUntrackedFiles = no
That solves most of those problems, as git will only consider files already in the repository and ignore everything else in your home directory.
This is what I do. It's simple enough and doesn't require any external dependencies. Highly recommend this approach.

The only issue I've had with this approach is that new files which I'd like to commit to the repository are ignored by default. One could use the .gitignore negation strategy like OP mentions to first unignore the file and then add it to the repository. I just end up using git add -f foo to force the addition even though foo is ignored.

git config status.showUntrackedFiles no
Don't track your home directory with Git if you have a habit of using git clean -xdf. Speaking from a bad experience.
Their Vim-specific recommendations are woefully out of date.
Can you provide a link to more current recommendations for Vim? I've used this article so I'd like to know what it got wrong.
I used vim years ago, and the list looks like it was written then.

e.g. the package management. vim-plug seems fine, it seems odd to me to mention the others. (Pathogen's readme now mentions that it recommends vim's built-in package management. Vundle and neoplug haven't had a commit in years).

In terms of vim distributions, https://github.com/SpaceVim/SpaceVim probably deserves a mention, as inspired by the popular Spacemacs.

It'd probably be worth mentioning NeoVim.

(comment deleted)
I highly recommend Mathias Bynens's dotfiles under the Inspirations section. Have been using it for many years now and it makes it so much faster for me to work in terminal.
I have my home directory as a git repo: works great, simple, no symlinking
I have this as well. Easy to push and pull changes. I push to a private git server.

I keep a second copy repo on a thumb drive so I can start my home directory on an offline computer.

Interesting. But I guess there must be files that you don't check in because of size?
You can .gitignore everything. I used to have my homedir as git repo, but abadoned that because it interfered with other git repos inside my home. Nowadays i have ~/.config as git root, with some symlinks supporting the programs that don't look there on their own.
after trying a few different ways i've settled on chezmoi which they also mention on their utilites page. it supports quite a lot of things that many other solutions miss like templates so that you can use the same repo across different operating systems and being able to run scripts just once, in order and so on.

It's somewhat involved to set up but now across windows/linux/macos my entire setup is literally just a one-liner.

I really like chezmoi. It is powerful enough to setup an entire organizations dot files.
I started using Chezmoi as well a year or so ago. Its the only one I've managed to stick with and not end up with a mess of different setups across machines.
It took me 20 years to have an important realization about version controlled dotfiles: what I wanted to do was manage a set of sensible, personalized, non-standard behaviours for each tool I used, and have a way of distributing those across systems.

What I didn’t need at all, after all, was anything that installed different things on different hosts or classes of hosts.

Luckily, every tool I want to configure has a config file option to include another file, so I have a working copy of my version controlled defaults here:

  ~/.config/defaults
…and a one time installation script that makes various tool’s config files look like this:

  # begin defaults
  source ~/.config/defaults/tool.conf
  # end defaults
  
  local specific config here
I am quite happy managing the local bits by editing files on local machines. All my configuration stuff splits into either “used everywhere” or “used just on this one host”, so that makes it easier.

As soon as I figured this out I had to pinch myself as to why it had taken so long to come up with this idea.

I do something similar, though I use the "defaults" as the starting point and add overrides in local files which aren't checked in. Something like this:

    # .dotfiles/.bashrc
    # shared baseline config...
    if [ -f ~/.bashrc.local ]; then
        . ~/.bashrc.local
    fi
I still want the versioning of host-specific stuff though, as well as the backup (by having the repo on GitHub too), and easy reference to other hosts' config that I might want on my current one after all (or setting it up for the first time).

I do this by having ~/.config/tool/blah.conf as you'd expect; but also ~/.config/by-host/$hostname/tool/blah.conf

I agree 'classes of hosts' is way overkill for probably anyone's personal machines though. Only thing close to that I do is with aconfmgr for more system-oriented stuff - but that's all simply on the basis 'if SSD enable TRIM' type 'classes', no manual specification.

A few years ago I spent some time developing my own solution to this, which I published on GitHub. I drew inspiration from the best dotfiles managers at the time, but I found deficiencies in all of them which is why I made my own. Basically: no configuration file, uses symlinks and not copies or templates, and can clean up files after you remove them. The result is dfm [0]. I talk more about the differences from other high-quality projects in the README. If you like DFM, I’d appreciate a star on GitHub so that I can hit 100 to qualify to be listed on the dotfiles.github.io list :)

[0] https://github.com/cgamesplay/dfm

This is fascinating to me. I have been a unix user since 1994 and I have carried my dotfiles around with me. I almost never edit them. Things like "oh-my-zsh" just look to me like supply chain attacks as a service. What is the benefit of these dotfile schemes?
> What is the benefit of these dotfile schemes?

Not everyone has the time to create their own custom dotfiles.

Some just want sane and beautiful terminal defaults.

I certainly hope I did not give the impression that I spent a great deal of time editing these files. My zshrc is three lines, the last of which was added in the 90s.
Literally was just looking at doing this an hour ago. After deciding it’d be too much work I come to hn for my pre-sleep reading and find this, lol.
(comment deleted)
My pick is yadm - clean way to setup working environment on any host any *nix-like OS and bootstrap it in seconds. password-store (plus yubikey) organically complements it with simple and effective way to access secrets on any host. However I'm using private gitea instance to store repos for both. Just because. :)
There are so many utilities. Why not just use ansible? It can manage your dotfiles and much more (you can configure your whole machine).
Ansible is wonderful. I'd strongly consider it if I wasn't against the idea of having interesting enough dotfiles to version.

What I do with RasPi servers is I make a pull and a push playbook that live in a repo, which let me deploy what I have onto a new server, and pull any changes made in server web GUIs into the repo where I can commit them.

Then of course any other random periodic task also becomes an Ansible task.

If I had a use case for lots of random little scripts on my main machine, I think I'd choose Ansible over bash where practical, you get so many tools premade, and you can run local or remote all the same.

I’m slowly trying to make ansible playbook that pulls in my dot files and installs everything I need (node, rust, go, all my CLTs, etc).
I used to manage my dotfiles with stow (xstow actually) and do some simple links. Now I run an Ansible playbook that creates links or copy depending on the files and also install tools on different OSes.

The big benefit is that it can be organized in a modular way with roles and tags, and sensitives files (like my gpg keys...) can be encrypted with the vault.

I create my .vimrc and .tmux.conf when I set up my machine or when I get annoyed at the defaults.

I find the problem with "set and forget" kind of jobs is that you forget how to do something because someone else already did it for you, so you don't learn anything. It's similar to joining a complicated project where everything has been set up for you and all problems were solved and if you were to join a new project from day 1 you would have to learn everything again. (See setting up CI/CD, the structure of a java project, a gradle file, a maven POM, Dockerfiles, git, Kubernetes manifests and other devops tasks)

You still need to learn how to do things. I want to be fluent in the tools I use, so I would rather do things from memory.

Shameless plug for "yet another" dotfile manager I'm writing. It doesn't get in the way, and allows you to manage them in quite a flexible way. Templating, conditional linking, copying, custom code execution... You name it!

https://github.com/cquintana92/dotfilers

Just wanted to mention Stow[1] here, which I use to symlink my dotfiles on a new machine on a per app basis for many years now. My set up script just installs basic compiler toolchain and runs a bunch of stow commands based on Linux/Mac OS. I also adhere to XDG base spec[2] so I can keep my ~/ clean.

1: https://www.gnu.org/software/stow/ 2: https://wiki.archlinux.org/title/XDG_Base_Directory

Stow is the answer as far as I'm concerned. It's been around forever, it's standard, available in every package manager, the defaults just work.

I use it in combination with a make to automate the setup of a new install.

Yeah, I don't understand why people like reinventing the wheel, Stow works, Make works, both are available everywhere, you don't need 30 layers of JS to manage dotfiles, I'm not against progress, but at least make something better than what's already around.
Also, the latest version (>= 2.3.0, released June '19) has a new flag specific to managing dotfiles. When passing `--dotfiles`, stow replaces `dot-` at the beginning of filenames with `.` in the symlinks. The online help does not mention it, but the release notes and man pages do.
I do not like comments, which are basically upvotes, but yes, Stow is the answer and this should be the top comment.
I really like stow. It handles almost everything for me.

One thing, I didn't split things by apps per se. I split them based on task. In some machines, I do Python work, so I created a 'python' package. In some I do PHP, so I created a 'php' one. I have a 'base' one where it has my .bashrc, .profile, .bash_profile, fish configs and other things.

Everything in the .bashrc. .profile, etc is loaded around conditionals. So, while the files are long, the section only activates if the other package is available. I thought about splitting it onto files, but most editors have a way of collapsing sections, so I decided to keep it on one so it's easy to edit in one place.

How do you manage dotfiles of apps that overwrite symlinks? Eg: Xfce frequently does it.
weirdly, nobody mentioned vcsh[1] yet. it's a git-based tool that gives all git goodies. I use it and a couple of bash micro scripts to pull/push the latest changes upon logging in/out into shell (again bash, but seeking for POSIX or fish-based version)

[1] https://github.com/RichiH/vcsh

In 2015, GitHub sent me a DMCA takedown notice for a repository of dotfiles. When I tried to convince them how ridiculous that is, their only suggestion was to file a DMCA counter notice.

Glad they've come a long way since then.

Not much work ou-of-the-box.

In the home directory (~) run:

  git init
  git remote add origin https://github.com/username/dotfiles
  echo '/**' >> .gitignore;
  git add -f .gitignore
  git add -f .gitconfig
  git add -f .bash_profile
  # etc and push
On the second machine:

  git clone https://github.com/username/dotfiles tmp --no-checkout
  mv tmp/.git .
  rmdir tmp 
  git reset --mixed
I've used my custom bash script for managing dotfiles for ages and also have tried all other tools, but just couldn't get it right because I work on multiple OS. Then I found https://github.com/rhysd/dotfiles. It is the only tool I know of that can support multiple OS (Linux, mac, windows). I have to work with multiple OS and having a single dotfiles repo has been great, especially its ability to set different config on different OS, well as a unified Unix-like config setting.