Skip to content

Latest commit

 

History

History
577 lines (420 loc) · 22.1 KB

customization.asciidoc

File metadata and controls

577 lines (420 loc) · 22.1 KB

How to Customize homectl

So you’ve installed homectl according to the README instructions; now what? How do you apply it to your setup with a minimum of fuss?

This document describes how to create your own packages for homectl, or otherwise extend it to your liking. We’ll start by defining some key concepts, then dig into how homectl packages are constructed, and finally discuss various extension points ("hooks" and "triggers") that are available for you to use in the construction of your own packages.

Key Concepts

Before we begin, you should know a few important things:

  1. Everything you keep in homectl lives in a package, without exception. Packages provide functionality (including the hc command itself) for a specific purpose.

  2. Typically, packages live in your homectl git repository, which is called your setup.

  3. You use the hc command to enable and disable packages, or refresh your home directory after you make changes to a package.

When you first hc enable a package, homectl symlinks the contents of the package into ~/.homectl. Files from all your enabled packages will be mixed together under this directory. Then, files from your package’s overlay (discussed later) will be linked directly into your home directory.

When you hc disable a package, its files are unlinked from your home directory and from ~/.homectl, thus undoing the effects of hc enable.

As you modify your packages by adding/deleting/renaming files, you can use hc refresh to re-scan all your enabled packages and update your home directory and ~/.homectl.

Splitting Your Stuff Up

Before we talk about packages themselves, it’s worth taking a moment to think about how you want to organize your stuff. Consider splitting your stuff along these lines:

  • What it does (e.g. emacs vs. screen vs. ssh-agent vs. …​).

  • What hat you’re wearing (e.g. work, personal, charity).

  • How often you use it (keep rarely-used things out of your environment).

Don’t worry about platform compatibility at this stage (e.g. "this only works on Linux"). As we’ll see later, homectl can automatically include/ignore content based on platform.

Example 1. The Author’s homectl Packages
dev.hcpkg

Things related to software development. This includes scripts, Emacs packages and tweaks, etc.

emacs-ux.hcpkg

Themes, usability customizations and other enhancements for Emacs.

editor.hcpkg

Script for finding and starting an editor, depending on what environment he’s working in (GUI, text, …​).

fs.hcpkg

Various scripts for working with lots of files (renaming, moving, etc.)

mail.hcpkg

Various scripts for processing and archiving e-mail.

music.hcpkg

Scripts for manipulating the author’s music collection.

oh-my-zsh.hcpkg

A set of extensions to zsh maintained by robbyrussell, plus configuration.

root.hcpkg

Scripts needed only by the root user (e.g. backup scripts).

screen.hcpkg

.screenrc

ssh-agent.hcpkg

Ensures an ssh-agent is available in the author’s shell.

work.hcpkg

Scripts and configuration files relevant only to the author’s employment.

zsh.hcpkg

Shell customizations (on top of oh-my-zsh).

Anatomy of a Package

homectl packages are just directories with a .hcpkg extension. To create a new, empty package, all you need to do is:

$ mkdir my-stuff.hcpkg

The contents of a homectl package are broken down by system and by hook.

A system is a particular environment, such as a machine with a particular hostname, a particular OS, or a particular OS/CPU-architecture combination. Except for the default common system, which is always available no matter what machine you’re on, system names begin with an upper-case letter.

A hook is an extension point. Hooks are typically given well-known names, such as bin, lib or emacs. Hook names always begin with a lower-case letter.

All files in a homectl package belong to a particular system and hook. Except for the special common system, all files live in a directory hierarchy inside the package which follows the pattern:

example.hcpkg/$System/$hook/my-file.txt

Files in the common system can omit the $System directory entirely:

example.hcpkg/$hook/my-file.txt   # this file is in the "common" system

Any system or hook whose name begins with _ is ignored by homectl. So you can put stuff which you don’t want linked into ~/.homectl into one of these directories. Note that this does not apply to nested directories underneath hooks/systems; here are some examples:

example.hcpkg/_data               # not linked into ~/.homectl
example.hcpkg/foo/_data           # will be linked into ~/.homectl
example.hcpkg/Linux/_data         # not linked
example.hcpkg/Linux/foo/_data     # will be linked
Example 2. Layout of an example homectl package
example.hcpkg/
  bin/            <--- hook (in the "common" system)
    my-script

  emacs-startup/
    my-settings.el

  Linux/          <--- system
    bin/          <--- system-specific hook
      my-binary
    lib/
      libfoo.so
    ...

  _*/            <--- Directories matching this pattern are ignored

  [A-Z]*/        <--- this is what a system name looks like
    [a-z]*/      <--- this is what a hook name looks like
      ...
    _*/          <--- A sytem-specific ignored directory

  *.trigger      <--- Trigger files will be discussed later
  _trigger

Linking into ~/.homectl

When a package is enabled, homectl symlinks the contents of each package into ~/.homectl, following the pattern: ~/.homectl/$SYSTEM/$HOOK. From the example above, if you were to create a script example.hcpkg/bin/my-script, homectl would create the following link in ~/.homectl:

~/.homectl/common/bin/my-script -> path/to/example.hcpkg/bin/my-script

Note that unlike in the package, the common system is explicit here; this is so homectl can place its configuration files directly under ~/.homectl without fear of name clashes.

Similarly, if you were to place a file under a system-specific hook, you would see a symlink like so:

~/.homectl/Linux/bin/my-binary -> path/to/example.hcpkg/Linux/bin/my-binary

Symlinks created by homectl use relative paths when they are within your home directory, and absolute paths otherwise. This is done to accommodate a user whose home directory may be in different locations on different systems.

Subdirectories Inside Hooks

You may also place files in subdirectories inside a hook. Those files will be individually linked into ~/.homectl. This allows you to build entire trees with files pulled from different packages. For example, a binary package may place manpages under share/man/manX:

$ hc enable my-stuff.hcpkg
update /home/me/.homectl/enabled-pkgs
mkdir -p /home/me/.homectl/common/share/man/man1
ln -s ../../../home-setup/my-stuff.hcpkg/share/man/man1/foo.1 /home/me/.homectl/common/share/man/man1/foo.1
ln -s ../../../home-setup/my-stuff.hcpkg/share/man/man1/bar.1 /home/me/.homectl/common/share/man/man1/bar.1

The special overlay hook

The special overlay hook contains files that will be linked directly into your home directory when you enable the package. Typically, you would place dot-files here (e.g. .vimrc, .screenrc, etc.).

The overlay hook is only special in the common system — that is, system-specific overlay hooks will not be linked into your home directory.

Example 3. Placing a .screenrc into a homectl Package
$ mkdir screen.hcpkg
$ mkdir screen.hcpkg/overlay
$ touch screen.hcpkg/overlay/.screenrc

$ hc enable screen.hcpkg
...
ln -s ../../../home-setup/screen.hcpkg/overlay/.screenrc /home/me/.screenrc
...

If you place a .screenrc into the overlay, hc enable will link it into your home directory automatically.

As with subdirectories inside other hooks, only individual files are linked into your home directory; if you create a directory inside overlay/, a separate directory will be created in ~, and the files inside the overlay will be linked into that directory.

Example 4. Subdirectories in the Overlay
$ mkdir unison.hcpkg
$ mkdir unison.hcpkg/overlay
$ mkdir unison.hcpkg/overlay/.unison
$ touch unison.hcpkg/overlay/.unison/default.prf

$ hc enable unison.hcpkg
...
mkdir -p /home/me/.unison
ln -s ../home-setup/unison.hcpkg/overlay/.unison/default.prf /home/me/.unison/default.prf
...

homectl will create the directory if it doesn’t already exist, and place a symlink in that directory. We can also see that homectl has adjusted the symlink’s target path to account for the fact that it lives in a subdirectory.

Exercise: Creating a Package for tmux

Do you have a configuration file (or set of files) you’d like to keep in homectl? Now is a good time to try creating your own package. Let’s take tmux as an example; it keeps a configuration file in ~/.tmux.conf.

  1. Create a package for your tmux configuration:

    $ cd my-homectl-setup
    $ mkdir tmux.hcpkg
  2. Enable your new tmux package:

    $ hc enable tmux.hcpkg     # You can also shorten this to just "hc en".
  3. Create an overlay directory to hold your .tmux.conf:

    $ mkdir tmux.hcpkg/overlay
  4. Move your .tmux.conf into your homectl package:

    $ mv ~/.tmux.conf tmux.hcpkg/overlay/
    $ git add tmux.hcpkg/overlay/.tmux.conf
    $ git commit -m "Add my tmux.conf"
  5. Now, refresh your home directory so that homectl will re-scan your enabled packages and find your .tmux.conf:

    $ hc refresh    # or "hc ref", for short
        $ update /home/me/.homectl/enabled-pkgs
        $ ln -s ../../../my-homectl-setup/tmux.hcpkg/overlay/.tmux.conf
                /home/me/.homectl/common/overlay/.tmux.conf
        $ ln -s .homectl/common/overlay/.tmux.conf /home/me/.tmux.conf
  6. Notice that homectl created two symlinks: one from ~/.homectl to your .tmux.conf, and one from ~/.tmux.conf to ~/.homectl.

Finding Things in Enabled Packages

Often, you will want to write code that searches through all enabled packages looking for things. The hc path and hc tree commands help you do this by finding directories and/or files which pertain to specific hooks on the current system.

hc path: Finding Hook Directories

You can use the hc path command to generate a list of directories to search for a particular hook. hc path will always return hook directories in the common system, as well as hooks in other available systems (as outlined in the "Systems" reference later in this document).

For example, if you want to find all the available bin hooks on the current system, you might do this:

$ hc path bin
/home/me/.homectl/common/bin:/home/me/.homectl/Linux/bin

You can even use hc path to update environment variables:

$ echo $PATH
/usr/bin:/bin

$ hc path bin PATH
/home/me/.homectl/common/bin:/home/me/.homectl/Linux/bin:/usr/bin:/bin

If duplicates are present, hc path will helpfully remove them:

$ echo $PATH
/home/me/.homectl/common/bin:/home/me/.homectl/Linux/bin:/usr/bin:/bin

$ hc path bin PATH
/home/me/.homectl/common/bin:/home/me/.homectl/Linux/bin:/usr/bin:/bin

hc tree: Finding Specific Files

You can use hc tree to look for specific files inside hooks. For example, if you want to find all *.el files in the emacs-startup hook, you might do this:

$ hc tree emacs-startup '*.el'
/home/me/.homectl/common/emacs-startup/commit-message-mode.el
/home/me/.homectl/common/emacs-startup/graphviz-dot-mode.el ...

Formatting hc path and hc tree Output

Both hc path and hc tree take a number of different command-line options which can be used to format their output in a more favorable way for processing by other tools. See each command’s --help for further details.

Reference: Loader Packages

Some packages (called loader packages) exist only to provide a way for other packages to insert their functionality into your environment. For example, homectl comes with the loader-bash.hcpkg package. loader-bash.hcpkg replaces your existing .bashrc and .bash_profile with scripts that do nothing but load bash customizations from all your enabled homectl packages.

homectl includes several loader packages by default, to help get you started and to ease the task of breaking up your configuration into packages. You should choose the ones that apply to you and enable them with:

$ hc enable homectl/loader-foo.hcpkg

loader-bash.hcpkg and loader-zsh.hcpkg

These packages replace your shell’s standard profile and rc-files with stubs that search through your enabled homectl packages and load any shell customizations they find (environment variables, functions, shell scripts, snazzy prompts, etc.).

They look for shell customizations in the shell-env and shell-rc hooks, and load anything they find there. loader-bash.hcpkg will look for files named .sh or .bash, while loader-zsh.hcpkg will look for files named .sh or +.zsh*.

As the name implies, shell-env/* files are generally expected to contain environment variable or other settings that could apply to both interactive and non-interactive shells. They should not produce any output, nor expect any input. They may be run even as part of shell scripts, so it’s best to keep them as small as possible.

shell-rc/*, on the other hand, contain things one might use while sitting at a shell prompt. This would be a good place to change your prompt, set up an ssh-agent, or add shell aliases.

If the loader sees a bin/ hook inside your package, that directory will be automatically added to your PATH. Similarly, lib/, lib64/, etc. are added to your linker path. This helps you to package 3rd-party programs for use in homectl with a minimum of fuss.

loader-emacs.hcpkg

The Emacs loader replaces your ~/.emacs file with a script that loads Emacs packages and customizations from your enabled homectl packages. It also provides a convenient way to download and install package.el packages from third-party sources.

You can customize your Emacs by writing small a Emacs package (just a foo.el file with (provide 'foo) at the end) and placing it in the emacs-startup/ hook.

Reference: Systems

hc path, hc files and related commands will search for hooks in the following "system" subdirectories of ~/.homectl:

  • common

  • $system

    • e.g. Linux, Darwin

  • $system-$arch

    • e.g. Linux-i686, Darwin-x86_64

  • $system-$release

    • e.g. Linux-2.6.32, Darwin-13.3.0

  • $system-$release-$arch

    • e.g. Linux-2.6.32-i686, Darwin-13.3.0-x86_64

Reference: Hooks

The loaders that come with homectl support the following hooks. Any loaders you write should follow these conventions as well.

overlay/

Files in overlay are symlinked directly into your home directory. [common system only]

bin/

Added to $PATH. Contains scripts or binaries that belong to this package.

lib/
lib64/
lib32/

Added to $LD_LIBRARY_PATH, $DYLD_LIBRARY_PATH or the equivalent on your platform. Contains libraries used by binaries in this package.

emacs/

Added to Emacs’s load-path. You can place Emacs packages here and they will be accessible with (require).

emacs-startup/

Added to Emacs’s load-path. You can place Emacs packages here and they will be automatically loaded with (require) at startup.

shell-env/

Defines shell environment variables and other settings which apply to both interactive and non-interactive shells. Files in this hook should have an extension which matches the shell (e.g. *.sh, *.bash, etc.)

shell-rc/

Defines shell aliases, functions, prompts, and other settings which apply to interactive shells. Files in this hook should have an extension which matches the shell (e.g. *.sh, *.bash, etc)

Reference: Triggers

If simple symlinking isn’t enough to deploy a package, homectl provides a way to run programs when packages are enabled (actually, refreshed) or disabled. Just create a program named _trigger in the top-level package directory.

Triggers are currently not system-specific, so they should be written in such a way as to apply to all platforms. They are also non-interactive (user input is not supported).

Triggers are run with the trigger name as their sole parameter (for example: _trigger build). They are always run from the top-level *.hcpkg directory (so pwd or the equivalent will tell you where your package lives).

The following triggers are supported:

build

Prior to symlinking the contents of a package into ~/.homectl, the build trigger is run. This trigger may create generated files inside the package itself, and homectl will pick up those files and symlink them. When a package is enabled, or when an already-enabled package is updated via hc refresh, homectl will look in the top level of each package directory for a file called refresh.trigger. If this file exists and is executable, homectl will run it after symlinking the package’s files into ~/.homectl.

refresh

After a package has been symlinked into ~/.homectl, the refresh trigger is run. If refresh.trigger modifies any of the files in the homectl package, homectl will not notice this until the next time hc refresh is run.

disable

Before a package is disabled and unlinked from ~/.homectl, the disable trigger is run.

clean

After a package is disabled and has been unlinked from ~/.homectl, the clean trigger is run. This is the place to remove build artifacts and do general cleanup of the package directory, if required.

Here are a couple examples showing how to write shell scripts and/or makefiles to be run as triggers.

Example: Shell Script

#!/bin/sh

case "$1" in
    build)
        # Things to do prior to enabling/refreshing the package
        ;;
    refresh)
        # Things to do when the package has just been refreshed/enabled
        ;;
    disable)
        # Things to do before the package is disabled
        ;;
    clean)
        # Things to do to cleanup after the package is disabled
        ;;
esac

Example: Makefile

#!/usr/bin/env make -f

# Put this file in the top level of your package's directory, name it
# "_trigger", and make it executable.

build:
        # Things to do prior to enabling/refreshing the package

refresh:
        # Things to do when the package has just been refreshed/enabled

disable:
        # Things to do before the package is disabled

clean:
        # Things to do to cleanup after the package is disabled

.PHONY: build refresh disable clean