diff options
-rw-r--r-- | Makefile | 20 | ||||
-rw-r--r-- | README | 152 | ||||
-rw-r--r-- | apt.conf | 2 | ||||
-rwxr-xr-x | etckeeper | 26 | ||||
-rw-r--r-- | etckeeper.1 | 31 | ||||
-rw-r--r-- | foo/changelog | 5 | ||||
-rw-r--r-- | foo/compat | 1 | ||||
-rw-r--r-- | foo/control | 20 | ||||
-rw-r--r-- | foo/copyright | 5 | ||||
-rwxr-xr-x | foo/rules | 33 | ||||
-rwxr-xr-x | init.d/10restore-metadata | 5 | ||||
-rwxr-xr-x | init.d/20git-init | 6 | ||||
-rwxr-xr-x | init.d/30git-perm | 3 | ||||
-rwxr-xr-x | init.d/40git-ignore | 14 | ||||
-rwxr-xr-x | init.d/40git-pre-commit-hook | 16 | ||||
-rwxr-xr-x | init.d/50git-add | 5 | ||||
-rw-r--r-- | init.d/README | 13 | ||||
-rwxr-xr-x | post-apt.d/50git-add | 5 | ||||
-rwxr-xr-x | post-apt.d/60git-rm | 10 | ||||
-rwxr-xr-x | post-apt.d/75git-commit | 9 | ||||
-rw-r--r-- | post-apt.d/README | 2 | ||||
-rwxr-xr-x | pre-apt.d/50uncommitted-changes | 15 | ||||
-rw-r--r-- | pre-apt.d/README | 2 | ||||
-rwxr-xr-x | pre-commit.d/10store-metadata | 16 | ||||
-rwxr-xr-x | pre-commit.d/10warn-empty-directory | 6 | ||||
-rwxr-xr-x | pre-commit.d/10warn-hardlinks | 7 | ||||
-rwxr-xr-x | pre-commit.d/10warn-special-file | 9 | ||||
-rw-r--r-- | pre-commit.d/README | 2 |
28 files changed, 440 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4497b0d --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +install: + mkdir -p $(PREFIX)/usr/lib/etckeeper/ + cp -a *.d $(PREFIX)/usr/lib/etckeeper/ + + install -D etckeeper $(PREFIX)/usr/bin/etckeeper + install -m 0644 -D apt.conf $(PREFIX)/etc/apt/apt.conf.d/05etckeeper + install -m 0644 -D etckeeper.1 $(PREFIX)/usr/share/man/man1/etckeeper.1 + + for dir in *.d; do \ + command=$$(echo "$$dir" | sed -e 's/\.d$$//'); \ + ln -sf etckeeper $(PREFIX)/usr/bin/etckeeper-$$command; \ + ln -sf etckeeper.1 $(PREFIX)/usr/share/man/man1/etckeeper-$$command.1; \ + done + + for dir in *.d; do \ + mkdir -p $(PREFIX)/etc/etckeeper/$$dir; \ + for file in $$dir/*; do \ + ln -sf /usr/lib/etckeeper/$$file $(PREFIX)/etc/etckeeper/$$file; \ + done; \ + done @@ -0,0 +1,152 @@ +etckeeper is a collection of tools to let /etc be stored in a git +repository. It hooks into apt to automatically commit changes made to /etc +during package upgrades. It uses `metastore` to track file metadata that +git does not normally support, but that is important for /etc, such as the +permissions of `/etc/shadow`. It's quite modular and configurable, while +also being simple to use if you understand the basics of working with git. + + +## security warning + +First, a big warning: By checking /etc into revision control, you are +creating a copy of files like /etc/shadow that must remain secret. Anytime +you have a copy of a secret file, it becomes more likely that the file +contents won't remain secret. etckeeper is careful about file permissions, +and will make sure that repositories it sets up don't allow anyone but root +to read their contents. However, you *also* must take care when cloning +or copying these repositories, not to allow anyone else to see the data. + +Since git mushes all the files into packs under the .git directory, the +whole .git directory needs to be kept secret. Also, since git doesn't keep +track of the mode of files like the shadow file, it will check it out world +readable, before etckeeper fixes the permissions. The tutorial has some +examples of safe ways to avoid these problems when cloning an /etc +repository. + + +## what etckeeper does + +etckeeper has special support to handle changes to /etc caused by +installing and upgrading packages. Before apt installs packages, +`etckeeper-pre-apt` will check that /etc contains no uncommitted changes. +After apt installs packages, `etckeeper-post-apt` will add any new +interesting files to the repository, and commit the changes. + +git is designed as a way to manage source code, not as a way to manage +arbitrary directories like /etc. This means it has a few limitations that +etckeeper has to work around. These include file metadata storage, +empty directories, and special files. + +git has only limited tracking of file metadata, being able to track the +executable bit, but not other permissions or owner info. So file metadata +storage is handled by `metastore`. Amoung other chores, `etckeeper-init` +sets up a git hook that use `metastore` to store metadata about file +owners, permissions, and even extended attributes. This metadata is stored +in git along with everything else, and can be applied if the repo should +need to be checked back out. + +git cannot track empty directories. So `etckeeper-init` also sets up a git +hook to run `etckeeper-pre-commit`, which checks for empty directories +before committing, and warn about them. You can then either ignore the +empty directory, if it's not significant, or put a file (such as +`.gitignore`) in the directory to enable git to track it. + +git doesn't support several special files that you _probably_ won't have in +/etc, such as unix sockets, named pipes, hardlinked files (but softlinks +are fine), and device files. Again git hooks are used to warn if your /etc +contains such untrackable special files. + + +## tutorial + +A quick walkthrough of using etckeeper. + + cd /etc + etckeeper-init + +This `etckeeper-init` command initialises an /etc/.git/ repository. This +command is careful to never overwrite existing files or directories in +/etc. It will create a `.gitignore` if one doesn't already exist, sets up +git hooks if they don't already exist, and so on. It does *not* commit any +files into to git, but does `git-add` all interesting files for an initial +commit. So you might want to use git status to check that it includes all +the right files, and none of the wrong files. And you can edit the +.gitignore and so forth. Once you're ready: + + git commit -m "initial checkin" + git gc # pack git repo to save a lot of space + +After this first checkin, you can use regular git commands to check in +further changes: + + passwd someuser + git status + git commit -a -m "changed a password" + +Rinse, lather, repeat. + +etckeeper hooks into apt so changes to files in /etc caused by installing or +upgrading packages will automatically be committed. (`etckeeper-post-apt` +uses `git-add .`, so any new files in /etc that arn't gitignored will be +added. If it auto-adds files you don't want added, put them in +`.gitignore`.) + +You can use any git commands you like, but do keep in mind that, if you +check out a different branch or an old version, git is operating directly +on your system's /etc. Often it's better to clone /etc to elsewhere and do +potentially dangerous stuff in a staging directory. You can clone the +repository using git-clone, but be careful that the directory it's cloned +into starts out mode 700, to prevent anyone else from seeing files like +shadow, before `etckeeper-init` fixes their permissions: + + mkdir /my/clone + cd /my/clone + chmod 700 . + git clone /etc + etckeeper-init + chmod 755 . + +Another common reason to clone the repository is to make a backup to a +server. When using git-push to create a new remote clone, make sure the new +remote clone is mode 700! (And, obviously, only push over a secure +transport like ssh.) + + ssh server 'mkdir /etc-clone; cd /etc-clone; chmod 700 .; git init' + git push ssh://server/etc-clone master + +Of course, it's also possible to push from a server onto client machines, +to deploy changes to /etc. You might even set up branches for each machine +and merge changes between them. Once /etc is under version control, the +sky's the limit.. + + +## configuration + +Each etckeeper-foo command uses `run-parts` to run the executable files in +/etc/etckeeper/foo.d/. By default these directories contain a bunch of +symlinks to the actual files; you can remove or reorder the symlinks, +or add your own custom files. + +Note that the etckeeper-foo commands are careful to not hardcode anything about +git. If you want to use some other revision control system, that's +theoretically possible to accomplish by just changing the files in +/etc/etckeeper/. If you do this, please let me know. + + +## inspiration + +Two blog posts provided inspiration for techniques used by etckeeper: +* http://www.jukie.net/~bart/blog/20070312134706 +* http://bryan-murdock.blogspot.com/2007/07/put-etc-under-revision-control-with-git.html + +isisetup (http://www.isisetup.ch/) has some of the same aims as etckeeper, +however, unlike it, etckeeper does not aim to be a git porcelain with its +own set of commands for manipulating the /etc repository. Instead, +etckeeper provides a couple of simple tools and hooks for setting up an /etc +repsository, and then gets out of your way; you manage the repository using +regular git commands. + + +## author + +Joey Hess <joey@kitenet.net> diff --git a/apt.conf b/apt.conf new file mode 100644 index 0000000..96b7e14 --- /dev/null +++ b/apt.conf @@ -0,0 +1,2 @@ +DPkg::Pre-Install-Pkgs { "if [ -x /usr/bin/etckeeper-pre-apt ]; then etckeeper-pre-apt; fi"; }; +DPkg::Post-Invoke { "if [ -x /usr/bin/etckeeper-post-apt ]; then etckeeper-post-apt; fi"; }; diff --git a/etckeeper b/etckeeper new file mode 100755 index 0000000..0840e85 --- /dev/null +++ b/etckeeper @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +if [ "$(basename $0)" != etckeeper ]; then + command="$(basename $0 | sed -e s/etckeeper-//)" +else + if [ -z "$1" ]; then + echo "usage: etckeeper command" >&2 + exit 1 + fi + command="$1" + shift 1 +fi + +if [ ! -d "/etc/etckeeper/$command.d" ]; then + echo "etckeeper: /etc/etckeeper/$command.d does not exist" >&2 + exit 1 +fi + +if [ "$command" = post-apt ] || [ "$command" = pre-apt ]; then + cd /etc +elif [ "$command" = pre-commit ] && [ -n "$1" ]; then + chdir "$1" +fi + +run-parts "/etc/etckeeper/$command.d" diff --git a/etckeeper.1 b/etckeeper.1 new file mode 100644 index 0000000..bcecf59 --- /dev/null +++ b/etckeeper.1 @@ -0,0 +1,31 @@ +.\" -*- nroff -*- +.TH ETCKEEPER 1 "" "" "" +.SH NAME +etckeeper \- keep /etc in git +.SH SYNOPSIS +.B etckeeper command [args] +.SH DESCRIPTION +etckeeper is a collection of tools to let /etc be stored in a git +repository. Please see its README for more detailed information. +.SH COMMANDS +.TP +.B init +This is the only command you typically need to run by hand. It initialises +a git repository for the current directory. Typically this is run in /etc +once when starting to use etckeeper on a machine. It can also be used to +initialise a clone of the /etc repository. +.TP +.B pre-commit +This is called as a git pre-commit hook. It should be passed the name +of the toplevel directory of the repository (typically /etc). It stores +metadata and does sanity checks. +.TP +.B pre-apt +This is called by apt's DPkg::Pre-Install-Pkgs hook. It allows committing +any uncommitted changes in /etc before the apt run. +.TP +.B post-apt +This is called by apt's DPkg::Post-Invoke hook. It commits changes made by +packages to the repository. +.SH AUTHOR +Joey Hess, <joey@kitenet.net>. diff --git a/foo/changelog b/foo/changelog new file mode 100644 index 0000000..0925dbf --- /dev/null +++ b/foo/changelog @@ -0,0 +1,5 @@ +etckeeper (0.1) unstable; urgency=low + + * First release. + + -- Joey Hess <joeyh@debian.org> Mon, 05 Nov 2007 16:43:04 -0500 diff --git a/foo/compat b/foo/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/foo/compat @@ -0,0 +1 @@ +5 diff --git a/foo/control b/foo/control new file mode 100644 index 0000000..01735ab --- /dev/null +++ b/foo/control @@ -0,0 +1,20 @@ +Source: etckeeper +Section: admin +Priority: optional +Build-Depends: debhelper (>= 5), dpkg-dev (>= 1.9.0) +Maintainer: Joey Hess <joeyh@debian.org> +Standards-Version: 3.7.2 +Vcs-Git: git://git.kitenet.net/etckeeper + +Package: etckeeper +Architecture: all +Section: admin +Depends: metastore, git-core, ${misc:Depends} +Description: store /etc in git + etckeeper is a collection of tools to let /etc be stored in a git + repository. It hooks into apt to automatically commit changes made to /etc + during package upgrades. It uses `metastore` to track file metadata that + git does not normally support, but that is important for /etc, such as the + permissions of `/etc/shadow`. It's quite modular and configurable, while + also being simple to use if you understand the basics of working with git. + diff --git a/foo/copyright b/foo/copyright new file mode 100644 index 0000000..9ff61e0 --- /dev/null +++ b/foo/copyright @@ -0,0 +1,5 @@ +Files: * +Copyright: © 2007 Joey Hess <joey@kitenet.net> +License: GPL-2+ + The full text of the GPL is distributed as doc/GPL in etckeeper's source, + and is distributed in /usr/share/common-licenses/GPL-2 on Debian systems. diff --git a/foo/rules b/foo/rules new file mode 100755 index 0000000..a759dac --- /dev/null +++ b/foo/rules @@ -0,0 +1,33 @@ +#!/usr/bin/make -f + +build: + dh_testdir + +clean: + dh_testdir + dh_testroot + dh_clean + +binary-arch: build + +binary-indep: build + dh_testdir + dh_testroot + dh_clean -k + $(MAKE) PREFIX=debian/etckeeper + dh_installdocs README + dh_installexamples + dh_installchangelogs + dh_compress + dh_fixperms + dh_installdeb + dh_gencontrol + dh_md5sums + dh_builddeb + +# Not intended for use by anyone except the author. +announcedir: + @echo ${HOME}/src/joeywiki/code/etckeeper/news + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary diff --git a/init.d/10restore-metadata b/init.d/10restore-metadata new file mode 100755 index 0000000..fdb1f8e --- /dev/null +++ b/init.d/10restore-metadata @@ -0,0 +1,5 @@ +#!/bin/sh +set -e +if [ -e .metadata ]; then + metastore --apply +fi diff --git a/init.d/20git-init b/init.d/20git-init new file mode 100755 index 0000000..69867c4 --- /dev/null +++ b/init.d/20git-init @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +if [ ! -e .git ]; then + git-init + echo "$(hostname) /etc repository" > .git/description +fi diff --git a/init.d/30git-perm b/init.d/30git-perm new file mode 100755 index 0000000..564e489 --- /dev/null +++ b/init.d/30git-perm @@ -0,0 +1,3 @@ +#!/bin/sh +set -e +chmod 700 .git diff --git a/init.d/40git-ignore b/init.d/40git-ignore new file mode 100755 index 0000000..88866cf --- /dev/null +++ b/init.d/40git-ignore @@ -0,0 +1,14 @@ +#!/bin/sh +set -e +if [ ! -e .gitignore ]; then + cat >.gitignore <<EOF +*~ + +# new and old versions of conffiles, stored by dpkg +*.dpkg-* + +# mount(8) records system state here, no need to keep these in git +blkid.tab(|.old) +mtab +EOF +fi diff --git a/init.d/40git-pre-commit-hook b/init.d/40git-pre-commit-hook new file mode 100755 index 0000000..88eb581 --- /dev/null +++ b/init.d/40git-pre-commit-hook @@ -0,0 +1,16 @@ +#!/bin/sh +set -e +if [ -x .git/hooks/pre-commit ]; then + if ! grep -q etckeeper-pre-commit .git/hooks/pre-commit; then + echo "etckeeper warning: .git/hooks/pre-commit needs to be manually modifed to run etckeeper-pre-commit" >&2 + fi +else + cat >.git/hooks/pre-commit <<EOF +#!/bin/sh +# pre-commit hook for etckeeper. Calls etckeeper-pre-commit to store metadata +# and do sanity checks. +set -e +etckeeper-pre-commit `pwd` +EOF + chmod +x .git/hooks/pre-commit +fi diff --git a/init.d/50git-add b/init.d/50git-add new file mode 100755 index 0000000..06504b4 --- /dev/null +++ b/init.d/50git-add @@ -0,0 +1,5 @@ +#!/bin/sh +set -e +if ! git-add .; then + echo "etckeeper warning: git-add failed" >&2 +fi diff --git a/init.d/README b/init.d/README new file mode 100644 index 0000000..90aec67 --- /dev/null +++ b/init.d/README @@ -0,0 +1,13 @@ +Executable files in this directory are run to initialise the working directory +for use by etckeeper. If the working directory is not already in version +control, that includes setting up the version control, but not actually +committing anything. If the working directory is in version control, +it includes applying stored metadata to the checked out files in the +working directory. + +Please be careful to *never* overwrite existing files/directories +in the working directory (or use absolute care when doing so). If a file +you need to write already exists, check if its contents are sane, and +if not, emit a warning on stderr. + +If initialisation fails, exit nonzero and no later files will be run. diff --git a/post-apt.d/50git-add b/post-apt.d/50git-add new file mode 100755 index 0000000..06504b4 --- /dev/null +++ b/post-apt.d/50git-add @@ -0,0 +1,5 @@ +#!/bin/sh +set -e +if ! git-add .; then + echo "etckeeper warning: git-add failed" >&2 +fi diff --git a/post-apt.d/60git-rm b/post-apt.d/60git-rm new file mode 100755 index 0000000..73dc4d8 --- /dev/null +++ b/post-apt.d/60git-rm @@ -0,0 +1,10 @@ +#!/bin/sh +set -e + +TAB=" " + +for file in $(git status | egrep "^#${TAB}*deleted: *" | sed 's/.*deleted: *//'); do + if [ ! -d "$file" ]; then + git rm "$file" + fi +done diff --git a/post-apt.d/75git-commit b/post-apt.d/75git-commit new file mode 100755 index 0000000..30ef0f2 --- /dev/null +++ b/post-apt.d/75git-commit @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +# TODO: figure out what packages were acted on by the apt run, and include +# that info in the commit message +message="committing changes after apt run" + +# ignore exit code since it exits nonzero if there is nothing to do +git commit -m "$message" || true diff --git a/post-apt.d/README b/post-apt.d/README new file mode 100644 index 0000000..befa5c0 --- /dev/null +++ b/post-apt.d/README @@ -0,0 +1,2 @@ +Files in this directory are run after apt has run. They should commit +changes and new files in /etc to repository. diff --git a/pre-apt.d/50uncommitted-changes b/pre-apt.d/50uncommitted-changes new file mode 100755 index 0000000..c8e4736 --- /dev/null +++ b/pre-apt.d/50uncommitted-changes @@ -0,0 +1,15 @@ +#!/bin/sh +set -e +if ! LANG=C git-status 2>&1 | grep -q "working directory clean"; then + git-status || true + echo "etckeeper warning: /etc is not clean" >&2 + printf "Press Enter to commit changes and continue. " + read line </dev/tty + git add . + if ! git commit -m "saving uncommitted changes in /etc prior to apt run"; then + echo "etckeeper warning: git commit failed" >&2 + echo "Please resolve the uncommitted changes by hand." + printf "Press Enter when ready to continue. " + read line </dev/tty + fi +fi diff --git a/pre-apt.d/README b/pre-apt.d/README new file mode 100644 index 0000000..47001b4 --- /dev/null +++ b/pre-apt.d/README @@ -0,0 +1,2 @@ +Files in this directory are run before apt is run. This is mostly used for +sanity checks, ie, does /etc have any uncommitted changes? diff --git a/pre-commit.d/10store-metadata b/pre-commit.d/10store-metadata new file mode 100755 index 0000000..7958888 --- /dev/null +++ b/pre-commit.d/10store-metadata @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +# ensure the file exists so that it will list its own metadata +if [ ! -e .metadata ]; then + metastore --save + # the file could leak hidden dir contents.. + chmod 600 .metadata +fi + +# metastore doesn't produce the same output file for the same metadata +# everytime, so avoid changing the file if nothing really changed. +if [ ! -z "$(metastore --compare)" ]; then + metastore --save + git add .metadata +fi diff --git a/pre-commit.d/10warn-empty-directory b/pre-commit.d/10warn-empty-directory new file mode 100755 index 0000000..b850c86 --- /dev/null +++ b/pre-commit.d/10warn-empty-directory @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +empty=$(find -type d -empty | grep -v /.git/) || true +if [ -n "$empty" ]; then + echo "etckeeper warning: there are some empty directories, which git will ignore" >&2 +fi diff --git a/pre-commit.d/10warn-hardlinks b/pre-commit.d/10warn-hardlinks new file mode 100755 index 0000000..3dd7a96 --- /dev/null +++ b/pre-commit.d/10warn-hardlinks @@ -0,0 +1,7 @@ +#!/bin/sh +set -e +hardlinks=$(find -type f -not -links 1 | grep -v /.git/) || true +if [ -n "$hardlinks" ]; then + echo "etckeeper warning: hardlinked files could cause problems with git:" >&2 + echo "$hardlinks" >&2 +fi diff --git a/pre-commit.d/10warn-special-file b/pre-commit.d/10warn-special-file new file mode 100755 index 0000000..cb4d019 --- /dev/null +++ b/pre-commit.d/10warn-special-file @@ -0,0 +1,9 @@ +#!/bin/sh +set -e +special=$(find -not -type d -not -type f -not -type l | grep -v /.git/) || true +if [ -n "$special" ]; then + echo "etckeeper warning: special files could cause problems with git:" >&2 + echo "$special" >&2 +fi + +true diff --git a/pre-commit.d/README b/pre-commit.d/README new file mode 100644 index 0000000..051d094 --- /dev/null +++ b/pre-commit.d/README @@ -0,0 +1,2 @@ +This is run by a git pre-commit hook before committing changes to the +repository. This can be used for storing metadata, and for sanity checks. |