The main functionality
======================

`pristine-etc-keeper` is a program that hooks into `etckeeper` to
maintain a git branch that is a "pristine" version of `/etc`--just the
raw files provided by installed packages.

It makes several assumptions that make it a bit less general than
etckeeper:
 - the real directory being tracked by etckeeper is `/etc`
 - the VCS using used is `git`
 - the system package manager is `pacman`

It works by asking `pacman` which packages are installed, and
extracting `/etc` files from the cached copies of the package
tarballs.  A litteral pristine version of `/etc` lives at
`/var/lib/pristine-etc/etc`.

Invocation
==========

The normal operation of pristine-etc-keeper is for it to be called by
etckeeper's post-install hook.  You usually don't need to worry about
it.

However, sometimes it may be useful to call it from somewhere else.

The `pristine-etc-keeper` program essentially has two modes of
operation:

 1. With arguments: Run, and use "$*" for the commit message.

 2. No arguments: If a run was previously requested, but never
    fulfilled; do that now.

Really though, if it has arguments, it requests a run with that commit
message; then, whether it requested a run or not, it launches another
process to fulfill any outstanding requests.

If you're thinking "What!?  Why request runs?  Why not just run
directly?  It's needless complexity!"  Instinctively, I'd agree with
that line of reasoning; but see "The spool" below for justification of
the complexity.

Implementation details
======================

The spool
---------

It would be really simple to just provide a program that you run when
you want to update the pristine-etc.  Unfortunately,
pristine-etc-keeper is very slow, and this would make many tasks
painful.  So we run it asyncronously.

But that opens a whole other slew of problems.  What happens if we try
to run it again while it is already running?  The second instance
should wait until the first instance is finished.  But only one
instance should be queued at a time; if a 3rd instance tries to start,
it should just be discarded/merged with the one already waiting
("coalescing").

A simple thing to do would have been to write a daemon that is always
running, waiting to receive a signal that it should run.  I don't like
that because I don't really want the process to be long lived, nor for
it to be started at boot.

But, let's take that idea, tweak it a bit.

Let's conceptualize the "signal" as the invoker adding an item to a
filesystem spool; each time the daemon runs a job, it empties the
spool.

Now, we modify this idea by saying that the daemon may exit when the
spool is empty (ie, it has no work to do, and would just idle-wait
until it is asked to run again).  When the invoker adds an item to the
spool, we simply have it ensure that the daemon is running.

To translate this into the source code names, the invoker is "fill"
because it adds an item to the spool, and the so-called-daemon that
runs the jobs is "drain" because it drains the spool.

Systemd integration
-------------------

In the above section, we conceptualized the "drain" program as a
daemon, even though it isn't truly one.  This conceptualization is
useful in administration as well.

So, if systemd is being used, we have it litterally show up as
pristine-etc-keeper.service.  This has a number of benefits:

 - cgroup isolation
 - uniform logging with other system services
 - most importantly: systemd v230 and up won't kill a run in progress
   when you log out

But, if systemd wasn't used to boot the system, then it simply forks
to the background.

If you'd like to see similar integration with another service manager,
patches welcome!
