@ wrote... (5 years, 3 months ago)

This pattern which I thought up (but is likely not unique) can very nicely manage your service configurations, automatically reload/restart your service, and even give non-root users (if desired) the ability to modify system config files.

All through the magic of git hooks. This example tracks config files for Apache (httpd).

The general flow of events is the following:

  1. try to commit a change to your local repo
  2. this change is tested for validity before being allowed into the repo
  3. if the tests pass great, you now have an updated repo
  4. push this change to your master repo
  5. this push triggers an update in the service repo

The following proof on concept was done on Fedora.


For these scripts I use plumbum but feel free to re-implement these ideas any way you see fit.

# `easy_install pip` if required
pip install plumbum

Create the initial git repo in your httpd directory.

cd /etc/httpd
echo run >> .gitignore
echo logs >> .gitignore
echo modules >> .gitignore
git init .
git add * # this won't actually work, add all files/dirs individually
git commit -a -m "initial commit"

Now create the master repo, which needs to be bare.

mkdir /var/lib/git # you can of course put this wherever you want
git clone --bare file:///etc/httpd /var/lib/git/httpd.git

And point the service repo to the master

cd /etc/httpd
git remote add origin file:///var/lib/git/httpd.git

Now create your working repo

git clone file:///var/lib/git/httpd.git httpd

So now we have three repos:

  1. /etc/httpd which is our service repo

  2. /var/lib/git/httpd.git which is our master repo

  3. /root/httpd which is our local repo


Now we install the hooks, which is where the magic lives.

# /var/lib/git/httpd.git/hooks/post-receive:
#!/usr/bin/env python

from plumbum import local
from plumbum.cmd import git, httpd, service, sudo

with local.cwd( '/etc/httpd' ):
    print "cwd: ", local.cwd
    # there is some goofiness here that I don't understand
    # use sudo and []() syntax
    print sudo[ git['pull','--rebase']]() 
    print httpd('-t')
    print service('httpd','reload')

Make sure that file is executable via chmod +x /var/lib/git/httpd.git/hooks/post-receive

This is hard example because httpd has lots of files and they have some paths defined that lookup other files. Hence the need for sed.

# /root/httpd/.git/hooks/pre-commit:
#!/usr/bin/env python

import os, sys
import plumbum
from plumbum.cmd import git, sed, httpd

# bonus awesome class, read more here: /tech/tempdir-context-manager/
class TempDir(object):
    def __init__(self, suffix='', prefix='tmp', dir=None):
        import tempfile
        self.tdir = tempfile.mkdtemp(suffix, prefix, dir)

    def __enter__(self):
        return self.tdir

    def __exit__(self, type, value, traceback):
            import shutil
            shutil.rmtree( self.tdir )
            import sys
            print >> sys.stderr, "could not delete '%'" % self.tdir

with TempDir() as tdir:
        httpd_conf = os.path.join( tdir, 'httpd.conf' )
        git_dir = os.path.abspath( os.path.join( os.environ['GIT_DIR'], '..') )
        print "testing %s based on %s" % (httpd_conf,git_dir)

        # copytree requires dest to not exist
        shutil.rmtree( tdir )
        shutil.copytree( git_dir, tdir )

        with plumbum.local.cwd( tdir ):
            # change the environment for the test
            ln('-s', '/etc/httpd/modules' )
            ln('-s', '/etc/httpd/logs' )
            ln('-s', '/etc/httpd/run' )

        sed('-i', '-e', 's/^ServerRoot.*/ServerRoot "%s"/' % tdir.replace('/','\/'), httpd_conf)
        print httpd['-t', '-f', httpd_conf]()

    except Exception as e:
        print >> sys.stderr, str(e)

Again, make sure it executable.

If you want to enforce this pattern…

# /etc/httpd/.git/hooks/pre-commit:
#!/usr/bin/env python
import sys
print >> sys.stderr, "no commits allowed, use a local repo"


  • From /root/httpd make an edit.

  • git commit # only valid configs allowed in

  • git push # trigger service repo update and reload

  • ??

  • profit!

Sample output

[root@www /tmp/httpd]
# git commit -a -m "bad"
testing  /tmp/tmpkNb2gx/httpd.conf
Command line: ['/sbin/httpd', '-t', '-f', '/tmp/tmpkNb2gx/httpd.conf']
Exit code: 1
Stderr:  | AH00526: Syntax error on line 367 of /tmp/tmpkNb2gx/httpd.conf:
         | Invalid command 'asdf', perhaps misspelled or defined by a module not included in the server configuration

# git commit -a -m "good"
testing  /tmp/tmpOPk1be/httpd.conf
[master 1f02632] good
 1 file changed, 1 insertion(+), 1 deletion(-)

# git push
Counting objects: 7, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 365 bytes, done.
Total 4 (delta 2), reused 0 (delta 0)
remote: cwd:  /etc/httpd
remote: First, rewinding head to replay your work on top of it...
remote: Fast-forwarded master to 1f0263203bb98e5a8eaa9c28bec1c41c101d8a25.
To /var/lib/git/httpd.git/
   2a9d2d1..1f02632  master -> master
Category: tech, Tags: git
Comments: 0
Click here to add a comment