Sunday, July 8, 2007

Put /etc Under Revision Control (with git)

UPDATE: There is a much easier way now (though it uses bzr by default on Ubunutu, not git, in case you are really particular, but apparently it can be made to use git instead):

sudo aptitude install etckeeper

There, now etc is under revision control and changes are automatically committed with a helpful message after you install new packages. Thanks for the tip, Marcelo! Read on for the original blog entry.

I put my /etc directory under revision control. I used git. It's awesome. Not that I think I'm going to wholesale copy my /etc from one machine to another, nor because I'm going to branch my /etc directory and spend weeks developing some cool new feature (whatever that would look like...) and then merge it back into the main branch. No, it's mostly because I like the idea of having the big "undo" option that having things under revision control provides. Having to commit changes and write check-in comments when I make changes helps me stay more organized too. It's never good when I go in willy-nilly to make a "quick change" to some configuration file.

I relied heavily on this blog entry for using git, and this blog entry for using mercurial (hg). I decided to go with git because mercurial quietly ignores symbolic links, and it seems like there are just a few important symbolic links in /etc. It also seems that git handles these just fine. Oh, and I didn't choose bzr or darcs mainly because they are slow, and because I want to get to know git better.

Here's how I did it (on Ubuntu Feisty Fawn, do all this as root):

First, initialize the repository:

cd /etc
git init-db
chmod og-rwx .git

The chmod is so non-root users can't go poking around in your repository. Next, ignore a few files:

cat > .gitignore << EOF
# add other files if necessary, depends on your setup...

Do the initial commit:

git add .
git commit -a -m "initial import"

Set up a script to check in any change made when installing new packages. Put this in /etc/apt and call it something like, git-snapshot-script. Here is the script I use (UPDATED: Jul 11, 2007):

set -e
caller=$(ps axww | mawk '/aptitude|apt-get/ {for (i=5; i<=NF ; i++) printf ("%s ",$i); printf ("\n") }' | head -1)
git-add .

# the || true is to make sure we don't error out because of the set -e
# above
STATUS="$(git status)" || true

if echo $STATUS | grep '\(Changed\|Update\)' > /dev/null ; then
   echo "git-snapshot-script: found changed files"
   echo $STATUS
   git-commit -a -m "snapshot after: $caller"
   echo "git-snapshot-script: no changes"
echo "git-snapshot-script: done"

Then tell apt to run this script after it's invoked:

cat >> /etc/apt/apt.conf << EOF
DPkg {
          Post-Invoke {"cd /etc ; ./apt/git-snapshot-script";};

chmod +x /etc/apt/git-snapshot-script

Now, check in the script and the change to apt.conf:

git add .
git commit -a -m "set up apt to track changes with git automatically"

And that should do it.


Anonymous said...

Oi, achei teu blog pelo google tá bem interessante gostei desse post. Quando der dá uma passada pelo meu blog, é sobre camisetas personalizadas, mostra passo a passo como criar uma camiseta personalizada bem maneira. Se você quiser linkar meu blog no seu eu ficaria agradecido, até mais e sucesso. (If you speak English can see the version in English of the Camiseta Personalizada. If he will be possible add my blog in your blogroll I thankful, bye friend).

Bryan said...

Is this my first official comment spam? I believe it is. Maybe I'll leave it here as a memorial of sorts. But you follow-on spammers, don't expect any kind of enshrinement.

Phil said...

Thanks! I've been meaning to take a look at git and also to start versioning /etc, so this gives me an excuse to do both.

Bryan said...

I just noticed that there was a bug in my git-snapshot-script so that it wouldn't do anything if there were only new files added. It only caught changed files. Basically, the regexp in my grep wasn't good enough. I've fixed it now.

Anonymous said...

I do something similar myself. I only track files that I've modified from the default settings, though, so that I can use it as a backup solution (rather than tracking everything in /etc). Also, I accompany this with a script that "bootstraps" my system from a reinstall by installing packages, changing ownership of /etc/passwd, and so on.

Jakub Narebski said...

Why don't you use IsiSetup, git based /etc management tool?

Bryan said...

I had never heard of IsiSetup. Looks interesting, but I'm not sure I'd want to have to use a whole 'nother set of commands. Here are some links:

Anonymous said...

just trying to install the update-script for apt-get.

i made some smaller changes, the most significant should be:

caller=$(ps --no-header -p $PPID -o "%a")

instead of your pipeline.
might be faster, and sure is shorter.

feel free to use it if you like it.

btw: grep -q doesnt produce output -> no need for the i/o-redirection.


Anonymous said...

oh, and thanks for the great post.

(sometimes i just forget being polite)


Anonymous said...

oh and i replaced the

echo $STATUS | grep '\(Changed\|Update\)' > /dev/null


git status > /dev/null

as the former didnt work for me,
also the manpage said that the exit status of "git status" would be nonzero only with untracked changes.


Anonymous said...

I don't know which version did you try, but now mercurial seems to handle symlinks quite well :-)

Unknown said...

Have a look at the rpath distro for a complete operating system under a RCS

Anonymous said...


Would it have helped to post your changes as a patch!

Anonymous said...

Since this post shows up among the first results when googling "etc git" I guess it's appropriate to drop a link here: Debian now includes etckeeper, courtesy of Joey Hess and a whole lot other people. It supports Git as well as other VCSs. Recent versions don't require any setup to work, just install the package and everything under /etc is put under revision control. Of course you are free to customize if you wish.