Ask HN: What do you use to manage dotfiles?

91 points by polm23 ↗ HN

96 comments

[ 2.8 ms ] story [ 135 ms ] thread
Emacs Org-mode's org-babel functionality. I have a few org-mode files storing all the dotfiles, put under version control. I can update and deploy them from within Emacs.
This is the most interesting system I've heard about for dotfiles. How do you organize the org files? Having a single org file with all my dotfiles in it could be interesting but probably unwieldy. It could be interesting to use code block expansion for keeping secrets out of the dotfile source.

I keep my emacs init in org and I can't believe I've never thought of this.

fascinating. never heard of doing something like this, though i use emacs all day long (but not org mode -- didn't even know org mode could do this).
Interesting approach! Think you can provide us with a small working example, especially the 'deploy' part?
This is what I came up with over lunch. I'm going to try keeping all my dotfiles in one org file. I have an init hook that runs org-bable-tangle to re-export all the dotfiles after saving.

  ** Meta
     If you place the following code into your emacs init when saving the
     ~/.dotfiles.org file the dotfiles will all be exported.

     #+BEGIN_SRC emacs-lisp :tangle yes
       (defun dotfiles-hook ()
         "If the current buffer is '~/.dotfiles.org' the code-blocks are
       tangled."
         (when (equal (buffer-file-name)
                      (expand-file-name (concat (getenv "HOME")
                                        "/.dotfiles.org")))
           (org-babel-tangle)))

       (add-hook 'after-save-hook 'dotfiles-hook)
     #+END_SRC

  ** bashrc
    #+BEGIN_SRC conf :tangle ~/.bashrc
      export PATH=$HOME/bin:$PATH
    #+END_SRC

  ** tmux
    #+BEGIN_SRC conf :tangle ~/.tmux.conf
      unbind C-b
      set -g prefix C-t
      bind C-t send-prefix
    #+END_SRC
Thanks. Upvoted your solution, but I see some problems with this approach for my workflow:

1. I run emacs-server, so the init file is run only during restarts OR when I manually load it from emacsclient.

2. Re-exporting all dotfiles seems rather redundant. I prefer to selectively export individual dotfiles only when I have made changes to it. That's why I was interested in the 'deploy' part.

I am not familiar enough with Babel, but it definitely seems to offer a more centralized way of managing my dotfiles. It's going into TODO.

I see a trend of people maintaining a GitHub repo called `dotfiles` for their public configurations, myself included for zsh/tmux/vim/git. I haven't found a satisfactory way to sync secrets between machines other than via sneakernet.

https://github.com/jamescun/dotfiles

Have you tried syncthing perchance?
I use:

    git init --bare $HOME/.myconf
    alias config='/usr/bin/git --git-dir=$HOME/.myconf/ --work-tree=$HOME'
    config config status.showUntrackedFiles no
where my ~/.myconf directory is a git bare repository. Then any file within the home folder can be versioned with normal commands like:

    config status
    config add .vimrc
    config commit -m "Add vimrc"
    config add .config/redshift.conf
    config commit -m "Add redshift config"
    config push
And so one…

No extra tooling, no symlinks, files are tracked on a version control system, you can use different branches for different computers, you can replicate you configuration easily on new installation.

Honestly this is genius! I hadn't thought of doing it like that, thank you! I had resorted to the usual .cfg folder and a helper script to link/update everything.
Could you explain how this works? Won't this be putting the .bashrc in $HOME/.myconf/.bashrc, which won't get picked up by bash? (And similar for other dotfiles)
You could add a symlink in your $HOME folder
No, since the config alias runs git with the option "--work-tree=$HOME", which tells it that the working directory is your home directory root, i.e. where config files (used to) live.

Anyway it's the proper root for config files, since if you use a .config directory (as seems to be the modern choice) that needs to live in your home directory of course.

To complete the description of the workflow (for others), you can replicate your home directory on a new machine using the following command:

   git clone --separate-git-dir=~/.myconf /path/to/repo ~
This is the best solution I've seen so far, and I may adopt it next time I get the itch to reconfigure my environment.
For posterity, note that this will fail if your home directory isn't empty. To get around that, clone the repo's working directory into a temporary directory first and then delete that directory,

    git clone --separate-git-dir=$HOME/.myconf /path/to/repo $HOME/myconf-tmp
    cp ~/myconf-tmp/.gitmodules ~  # If you use Git submodules
    rm -r ~/myconf-tmp/
    alias config='/usr/bin/git --git-dir=$HOME/.myconf/ --work-tree=$HOME'
and then proceed as before.
I found out exactly the same problem :)
Can you write a blog post on your workflow? I would love to learn more on how you to use it :P
I'm trying to reproduce it and documenting it as I go... ok I got it working now and I have notes. Stay tuned.
It's probably better if you do it then :-) I would probably skip some important parts trying to explain because I'm too much used to it already.
Working on it! I'll give you full credit for it and link to this thread for reference. Also let me know if you have a Twitter account you'd like me to reference.
Nice thanks. I don't have twitter. Also don't present me as the inventor of this technique because I'm not. I've read this long time ago on some dark corners of the internet :-) I'm just pointing it out.
Alright I'll be careful in my wording ;).
There are probably already posts about this: I didn't invented it, I read it somewhere… but it was long time ago, I don't remember where.
I'm confused - you can add files that are in a parent directory?
It's because we specify to git that the working tree is the home folder. For it the versionning doesn't happen in ".myconf", but directly in the home folder
"branches for different computers" sounds tedious if most changes are for every computer.
I have a "master" branch, and some "computer" branches. When changes are required for all computer, I do it in "master", and then update each branch by doing a "git merge master".
Why is this a bare repo as opposed to a normal one?
Because the working tree is already your home folder, you don't need to also have a copy of these files in ".myconf/".
So why use .myconf/ at all? What is it doing?
It contains the files that would normally be in .git (run `git help gitrepository-layout` for more details on the contents).
(comment deleted)
You mean, you are also adding pushing your .ssh dir to a remote repo?
Not at all. You can 'git add' (or in the case of the technique above 'config add') only the files and folders that are safely stored in a repository. Because of the 'ShowUntrackedFiles' flag, git/config won't always show folders you don't want to track, which would be annoying.
dotfiles repo on Github + GNU Stow
I have all my dotfiles in Dropbox and create symlinks to those. I require some 'quasi-secrets' in my configs and therefore I do not use Github.
Ansible and my own python script.

I use ansible, to template my gitconfig for different unix machines, and to install software that might be referenced in a dotfile

https://github.com/berdario/dotfiles/blob/master/setup/roles...

https://github.com/berdario/dotfiles/blob/master/setup/feynm...

(I have a separate branch for windows, but I found out that branches are not a good solution for this, since unlike feature branches, they'll never be truly merged... and unlike maintainance branches, they'll never stop being touched due to being out of maintanance)

And I use my own script, to also support Windows (since ansible supports windows targets, but cannot be used from Windows)... I defined this table with the destination for the symlinks (or, in the case of .ghci the destination where to copy it, since symlinking it wouldn't work)

https://github.com/berdario/dotfiles/blob/master/deploy.py#L...

Git and Xstow. I have a small shell script that parses the xstow.ini file and creates all the directories I have listed under the [keep-dirs] directive in order to prevent it from deleting empty directories or replacing them with links.

  #!/usr/bin/env bash
  
  #Read the keep directories from xstow.ini
  ini="$(<'xstow.ini')"
  IFS=$'\n' && ini=( ${ini} )
  ini=( ${ini[*]/\    =/=} )  # remove tabs before =
  ini=( ${ini[*]/=\   /=} )   # remove tabs after =
  ini=( ${ini[*]/\ =\ /=} )   # remove anything with a space around =
  
  #for each keep dir make sure it exists in the home dir
  for i in ${ini[@]}
  do
        if [[ $i =~ ^\ *dir ]]
        then
                eval $i
                mkdir $dir
        fi
  done
I try not to deviate from configuration default so that I don't need to manage any dot files.
I think no one has mentioned rcm[1]. I just maintain a private git repository cloned in .dotfiles in each system I own, and use rcm to set up symbolic links properly. It works pretty well with directories and lets you choose between creating and populating it with symlinks, or just symlinking the whole directory. For example, I can symlink the full .vim directory (including git submodules) and only link some files inside the .ssh directory (link the config file to my .dotfiles repo but leave ssh keys alone).

1: https://robots.thoughtbot.com/rcm-for-rc-files-in-dotfiles-r...

I've tried the other ones, but rcm is by far the most user friendly. I maintain a public and a private repo, and link them both into rcm. It works wonderfully.
I use Homesick (https://github.com/technicalpickles/homesick).

It's basically a dotfile manager. Symbolically links your stuff and runs scripts.

There's a fork called homeshick which is a plain-bash rewrite of homesick, so it doesn't have the ruby dependencies (for those who care about that). There might be few differences, though.
I use a git repository, cloned in .myconfigs, with a script that create a symlink for any file in it (apart from .git and a couple more)

https://github.com/riquito/configs

The usage is

    git clone git@github.com:username/configs.git ~/.myconfigs
    cd ~/.myconfigs
    ./reinstall.sh
Whenever I update the repository, maybe adding files, I run this in the other computers:

    cd ~/.myconfigs
    git pull --ff-only
    ./reinstall.sh
which simply refresh the symlinks (I should remove stale symlinks now that I think about it, for removed configurations - never happened yet)