caretaker - distributed dotfile and script manager, package format


(if you prefer technical infos over historical blah-blah, skip this section)

Actually, caretaker is just a dotfile manager, which however happens to support a sort of packages, version control, automatic sym- and hardlinking, and can also handle scripts and binaries.

It evolved from two hg repos for ~/bin and ~/etc and some management scripts. They served their purpose well, but at some point I decided that it'd be nice to only have the dotfiles/scripts actually used on a host. So I needed separate repositories for zsh configs, Xorg configs, etc. Managing them would probably be a little more work than previously, so it was time to write a script for that purpose, which is now called caretaker. It can handle as many git repos as you want, which may contain basically anything you can think of - you can even store movies in them, if you're up to that sort of weird VCS abuse ;-)

I myself use caretaker with repos containing various dotfiles and scripts / binaries.


caretaker requires two directories in your home directory. ~/bin contains symlinks to the executables shipped with your packages, and $PKG_DIR (~/packages by default) contains the packages themselves. ~/bin may also contain normal executables; caretaker will not overwrite existing files.


$PKG_DIR is the core of all this stuff. Its main use is storing the packages. There is one directory for each installed package, as created by git clone. $PKG_DIR holds two special files: .list and .list-remote. For an explanation about these files, see "THE PACKAGE LIST" below. It also contains a special directory, .collected - see "COLLECTED PACKAGE FILES".


All directories in $PKG_DIR must be valid git repositories which are not in the state of 'initial commit'. Dotfiles (directories starting with a .) are exempt from this, they will be ignored by caretaker.


The package root, in caretaker referred to as $PKG_ROOT, is structured just like the package directory $PKG_DIR, except that it neither contains .list nor .list-remote. It is the central point where caretaker fetches packages from and pushes packages to.

It should contain the pkglist script shipped in examples/. If it doesn't, PKGLIST_PATH in .caretaker.conf must be set to the appropiate location on the package root host.


The package list lives in the files .list and .list-remote mentioned above. It's used to decide whether a package needs to be pulled / pushed. Also, the 'ct add' completion relies on .list-remote, and back in the days when caretaker supported more than one DVCS, it was used to determine which DVCS to use for which package.

It consists of one line per package, each line containing three or four items separated by a single whitespace. The first item is the package name, the second one the repository type (DVCS), the third the current revision; in the remote list, the fourth is the URI used to access this package. Example:

  caretaker git 82d716d01dee0329af7df5e67b55558fe3ff1466 git://

The package list is generated by the script set in the config var $PKGLIST_PATH, by default examples/pkglist. Depending on $PKGLIST_LOCAL and $PKG_ROOT, it is either executed on the local host or (via ssh) on the remote host containing the package root. The script is always called with $PKG_PATH as the first argument. Its output must only contain valid pkglist lines (see the example above).

With $PKGLIST_LOCAL set to 1, there are some interesting possibilities. For instance, your pkglist script could contain a line like curl -s - so you can update your remote package list without having to use ssh.


Anything tracked with git can be used as package. However, as the purpose of caretaker is not to do your version control, you probably want to have at least one of the files and directories described below in it.


Special (as in, mostly handled by caretaker) directories and files in a package.

Note that all files and directories mentioned here are optional.


The place for executables to be in the user's PATH. caretaker will automatically create symlinks in ~/bin pointing to the files in the package's bin/. Also, if a file in bin/ contains valid POD, a manual will be generated out of it (see "COLLECTED PACKAGE FILES")


Configuration files. Unless your package contains a links file, all files in this directory (or, if it contains dotfiles, only the dotfiles) will automatically be symlinked as dotfiles from your home using checklinks(1). So, for example etc/vimrc will be installed as ~/.vimrc.

If you do not want this behaviour, create an empty links file. To disable it for all packages, set MAGIC_ETC=0.


Package hooks, see "HOOKS"


Manual files, separated by section (like man/7/caretaker.pod). Files with valid POD will be processed with pod2man, all other files which do not have a .pod suffix are assumed to be *roff source and directly put into the manual directory (with a symlink). See also "COLLECTED PACKAGE FILES"


Files for inclusion into other packages. See "PROVIDES"


Package description for ct info


Manual sym- and hardlink definitions. See checklinks(1)


If a Makefile is available, make will be executed every time the package is updated (ct add/push/pull/refresh)


The package's prerequisites, mainly dependencies. See "PREREQUISITES"


Package priority as an integer between 1 and 6. Packages with a priority above 3 require user confirmation to be removed


It is possible to have local packages which do not exist in the package root, as long as they have a working git origin. Note that they will always be pulled/pushed when doing batch pull/push, though.


The prerequisites are stored in a package in the file prereqs. It as an ordinary shell script which is sourced by caretaker's global post-update hook; so it will be sourced after pulling, pushing or refreshing a package.

Note that the file will be sourced in function scope. It is recommended to introduce parameters and options local to the prereqs script with typeset and setopt localoptions, respectively.

Its main use is to check for dependencies. To help with this, the following functions are available:

is_installed package

Returns true if package is installed, otherwise false

perlmodule perlmodule

Returns true if perlmodule can be used by perl, otherwise false

executable commendname

Returns true if commandname was found in the users PATH, otherwise false

offer_install package

Mark package for installation

depend expression | depend package package

Execute expression and automatically warn if it fails. In case of depend package, automatically mark package for installation if it isn't installed. If a depend fails, caretaker will inform the user about it and wait for confirmation

recommend ..., suggest ...

Take the same arguments as depend, but are of lower priority. recommend only causes "info" messages, and suggest does not interrupt caretaker to make sure it's read by the user

force_depend package package

Like depend, but installs the package without asking the user for confirmation.

Additionally, the string parameters warn and info can be used to store messages.

After executing the prereqs script, caretaker will print the content of these parameters and wait for confirmation. It will also offer to install packages marked by depend package or recommend package.


The provides directory contains subdirectories with the names of the package for which stuff is provided. Every time a package with a provides directory is added, updated or removed, for each of the directories in provides/, the respective package's post-update hook is exectued. It is the responsibility of that hook to do something useful with the data in provides/.


Hooks are little zsh snippets residing in $PKG_DIR/hooks which are sourced from within caretaker whenever needed.

Currently, the following hooks exist:


Sourced after a package was installed


Sourced after a package was updated. It is also sourced when adding a package (after post-add) and when calling ct refresh.


Sourced before a package is removed

The following helper functions can be used for data aggregation into files / directories. It should be noted that only the first argument is mandatory, they work fine with constructs like collect_into_file ${PKG_DIR}/.collected/ssh/config ${PKG_DIR}/*/provides/ssh/config(N).

collect_into_directory directory [files ...]

For each file, create an absolute symlink pointing to it in directory.

Note that directory must be used exclusively by this function: every time it runs, the directory will be wiped clean of all symlinks to make sure there are no broken ones (which would otherwise occur when removing a related package).

collect_into_file outfile [infiles ...]

Concatenate the content of infiles into outfile. If no infiles are specified, produces an empty outfile.


These files reside in $PKG_DIR/.collected (subject to change).

The directory is somewhat similar to ~/bin - it is automatically populated by caretaker. However, this one does not contain symlinks.

Currently, it contains only the directory man/, which holds the manual pages from the packages (extracted from bin/ and man/). This way, you can put .../.collected/man into you MANPATH to access manuals provided by packages.


caretaker uses git(1) as backend for storing and syncing package information. It is not recommended to use branches other than "master". In theory they should work, but will likely cause major confusion.


Birte Kristina Friesel <>


checklinks(1), ct(1)