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:
- try to commit a change to your local repo
- this change is tested for validity before being allowed into the repo
- if the tests pass great, you now have an updated repo
- push this change to your master repo
- 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
cd git clone file:///var/lib/git/httpd.git httpd
So now we have three repos:
/etc/httpdwhich is our service repo
/var/lib/git/httpd.gitwhich is our master repo
/root/httpdwhich 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
# /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): try: import shutil shutil.rmtree( self.tdir ) except: import sys print >> sys.stderr, "could not delete '%'" % self.tdir with TempDir() as tdir: try: 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) sys.exit(1)
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" sys.exit(1)
/root/httpdmake an edit.
git commit# only valid configs allowed in
git push# trigger service repo update and reload
[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. remote: remote: remote: To /var/lib/git/httpd.git/ 2a9d2d1..1f02632 master -> master