All Projects → divine-dotfiles → divine-dotfiles

divine-dotfiles / divine-dotfiles

Licence: MIT license
Divine.dotfiles: The Bash framework for dotfiles and everything Bash

Programming Languages

shell
77523 projects

Projects that are alternatives of or similar to divine-dotfiles

Vcsh
config manager based on Git
Stars: ✭ 1,836 (+9563.16%)
Mutual labels:  dotfiles-manager, dotfiles-installer, dotfiles-automation
dotman
dotman is a simple, elegant & easy to use dotfiles manager 🖖🏽
Stars: ✭ 85 (+347.37%)
Mutual labels:  dotfiles-manager, dotfiles-installer
dontfiles
Personal do(n)tfiles
Stars: ✭ 20 (+5.26%)
Mutual labels:  dotfiles-manager, dotfiles-automation
Greatness
Acheive greatness!
Stars: ✭ 107 (+463.16%)
Mutual labels:  dotfiles-manager, dotfiles-installer
dotfiles
The Dotfiles resources aggregate a collection of standalone 'dotfiles' to help you customize your system and related services into one cohesive and consistent approach.
Stars: ✭ 17 (-10.53%)
Mutual labels:  dotfiles-installer, dotfiles-automation
homesetup
Your shell good as hell ! Not just dotfiles.
Stars: ✭ 25 (+31.58%)
Mutual labels:  dotfiles-manager
dotbro
Dotbro - simple yet effective dotfiles manager.
Stars: ✭ 19 (+0%)
Mutual labels:  dotfiles-manager
dotfiles
Files that start with a dot (they're very cool).
Stars: ✭ 124 (+552.63%)
Mutual labels:  dotfiles-manager
pont
pont, the dotmodule manager
Stars: ✭ 17 (-10.53%)
Mutual labels:  dotfiles-manager
dot-templater
A small, portable Rust program intended for templating dotfiles across multiple systems.
Stars: ✭ 41 (+115.79%)
Mutual labels:  dotfiles-manager
dotfiles.sh
Dotfiles made easy
Stars: ✭ 66 (+247.37%)
Mutual labels:  dotfiles-manager
dotfiles
🌍 Look again at that dot. That's here. That's home. That's us.
Stars: ✭ 28 (+47.37%)
Mutual labels:  dotfiles-manager
dotao
WIP modern dotfiles manager
Stars: ✭ 15 (-21.05%)
Mutual labels:  dotfiles-manager
PSDotFiles
Bringing simple dotfiles management to Windows with PowerShell
Stars: ✭ 55 (+189.47%)
Mutual labels:  dotfiles-manager
DotFiles
Aliases, functions and shell utilities.
Stars: ✭ 22 (+15.79%)
Mutual labels:  dotfiles-manager
Yadm
Yet Another Dotfiles Manager
Stars: ✭ 2,982 (+15594.74%)
Mutual labels:  dotfiles-automation

Divine.dotfiles

In a nutshell

First-time users are very welcome to take a joy ride.

Divine.dotfiles leverages deployments.

A deployment is a Bash script with three specially named functions: one to check, another to install, third to remove. No assumptions are made about what a deployment does for a living. The return codes are used to communicate status back to the framework. As such, authoring a deployment is akin to implementing an interface.

The goals of Divine.dotfiles are:

  • The automation of setting-up any system that runs Bash.

    The intervention utility di is a practical tool for handling any number of deployments.

  • The cross-platformness within the Unix-like airspace.

    The built-in OS detection mechanism facilitates writing portable deployments.

  • The promotion of standards and best practices.

    The deployment ecosystem is designed with distribution and pluggability in mind.

The Divine bundles of deployments (distributed separately) strive to exemplify what a good deployment should look like.

Example deployment

Say, there is a need to maintain a certain command line utility on every machine. Below is a sample deployment that does a scaled down version of that:

# ~/.grail/dpls/example.dpl.sh

d_dpl_check() {
  [ -e ~/bin/cmd ] && return 1 || return 2
}

d_dpl_install() {
  cat >~/bin/cmd <<<'echo Divine.dotfiles rocks' && chmod +x ~/bin/cmd
}

d_dpl_remove() {
  rm -f ~/bin/cmd
}

And here is what working with it looks like:

Divine.dotfiles example 1

Dead simple. One wouldn’t need a framework for that. But there’s more.

Framework features

Table 1. Framework features

The Grail directory at ~/.grail/ is the one stop for configs, assets, and other dotfiles. The Grail contents are carried across OS’s and installed/removed via deployments.

A special kind of deployments, the Divinefiles maintain a set of system packages across machines and OS’s.

The deployments are ordered by their numerical priority, and the order is automatically reversed for the removal.

The deployments support basic grouping via flags.

The built-in OS detection mechanism allows to adapt to the growing list of supported OS distributions.

The Github repositories containing deployments are called bundles that can be attached with one command. One example is the associated Divine bundle essentials.

The deployments have access to a persistent key-value store, which can be used to track status between invocations.

The assets provide a way to separate the installation/removal logic from the content such as the configuration files.

Helper functions assist in implementing series of similar or dissimilar tasks.

Smart copying and symlinking

Specialized queues insert provided assets into target locations, while backing up pre-existing files and logging steps for future reversal.

Automated retrieval of Github repositories

A specialized queue either clones or downloads public repositories, depending on the available tools.

Framework design

The Divine.dotfiles framework is written in Bash. No attempt is made at POSIX compliancy, nor at cross-shell portability. Bash is chosen as the least common denominator across the modern operating systems.

At its very core, this framework is a sequential launcher of user-defined Bash code.

Since the deployments are essentially unrestricted Bash scripts, absolutely no illusion of security should be assumed. The framework creates a subshell for every sourced deployment, but security-wise that is pretty much it. Guarantees described in this section cover only the built-in mechanisms of the framework and the associated Divine bundles of deployments.

Interactivity

Divine.dotfiles is intended for interactive use, and no guarantee of unattended access is offered. The framework tends to prompt for user’s confirmation before major junctions in logic.

The --yes option is provided to auto-accept the more trivial framework prompts. The so-called urgent prompts — saved for potentially messy situations — ignore the --yes option and always interject.

Zero data loss

All framework components avoid deleting or clobbering any data that is not proven as recoverable. Instead, whenever files need to be replaced or removed, they are pushed to a backup location.

The deployment-specific backups are sent into the state directory. Other backups are created in place, by appending the .bak suffix. Whenever multiple backups stack up, an incrementing counter is added, e.g., <name>.bak-17.

The --obliterate option is available to suppress this behavior.

Non-interference

The framework does its best to not:

  • re-install something that appears already installed;

  • remove something that appears already not installed;

  • remove something that appears installed by means other than this framework;

  • touch anything that appears to have been manually tinkered with.

Alerts are printed whenever such cases are encountered.

The --force option opens a path to overcome these restrictions. However, even when the --force is applied:

  • an urgent prompt is issued for every instance of forced behavior;

  • the zero data loss policy still applies.

Reversibility

Where feasible, the built-in mechanisms that introduce changes to the system are paired with the reversal counterparts.

To give an example, any backups created when installing a copy-queue are restored to their original locations when that queue is removed. Another example is the introductory joy ride: if taken reasonably, it will leave the system in largely the same state as before installing the framework.

A glaring exception to this principle is the upgrading of the local package repositories through the system package manager (e.g., sudo dnf upgrade -y), which is deemed safe enough to overlook.

Race conditions

The framework mechanisms do not preclude race conditions, such as when the framework and another program operate on a file at the same time. For most applications, the risk of this approach becoming an issue is negligible.

Framework structure

Divine.dotfiles is installed, by default, into the ~/.divine/ directory, and is contained entirely in that directory, except:

  • A symlink to the framework’s main script, intervene.sh, is created somewhere on $PATH.

  • The Grail directory — home to user’s assets and deployments — is located, by default, at ~/.grail/.

  • Deployments may affect the system pretty much anywhere.

The installed framework consists of the following main parts:

Table 2. Framework structure

~/.grail/

The Grail directory (or simply, the Grail) houses the user’s deployments, assets, and persistent settings.

The Grail is designed to contain just enough data to replicate the current set of deployments, bundles, and assets on any system that runs Divine.dotfiles. Naturally, it is recommended to take the Grail under version control and sync it, e.g., via a cloud service or Github.

The Grail is sub-structured as follows:

  • assets/ — The directory for user’s assets, such as config files.

  • dpls/ — The directory for user’s custom deployments.

  • .stash.cfg — The Grail-level stash container, maintained by the framework.

  • .stash.cfg.md5 — The stash integrity checksum, maintained by the framework.

The location of the Grail directory can be permanently overridden by creating a file named .grail-dir-path in the root of the state directory. This file should contain the overriding path as its first line.

~/.divine/state/

The state directory stores the current state of deployment installations on current system. (The entire state directory is maintained by the framework.)

As this directory is automated and is specific to the machine it is maintained on, it is normally not needed to dive into it manually or version-control it. Still, this directory might come in handy in some emergency situations.

The state directory is sub-structured as follows:

  • backups/ — The directory for the deployment-level backups.

  • bundles/ — The directory for the copies of attached bundles of deployments.

  • stash/ — The directory for the key-value containers of the root-level and deployment-level stashing system.

~/.divine/lib/

This directory holds the guts of the framework. (The entire directory is, naturally, maintained by the framework.)

~/.divine/intervene.sh

The Divine intervention utility. This script is the command line interface to the framework. (The script file is, of course, maintained by the framework.)

A_DIR_ON_$PATH/di

The shortcut command: a symlink to the framework’s main script, intervene.sh. This symlink is normally created automatically, during the framework installation.

Installing and uninstalling the framework

System requirements

  • A Unix-like OS. The following OS distributions are openly supported:

    • Debian

    • Fedora

    • FreeBSD

    • macOS

    • Ubuntu

    This list is incomplete; you can help by expanding it.

    The framework will work on other operating systems too, but without the support for packages (e.g., the Divinefiles will not work).

  • Bash 3.2+ and either curl or wget.

    Git is not a hard requirement, but it is not a flaccid one either. Divine.dotfiles can be installed without Git. However, the framework will then proceed to vex the user with suggestions to auto-install it, until some day the y key is finally pressed.

Installing the framework

The following single shell command installs the Divine.dotfiles framework:

bash -c 'TMP=$(mktemp); URL=https://raw.github.com/divine-dotfiles/divine-dotfiles/main/lib/install/install.sh; if curl --version &>/dev/null; then curl -fsSL $URL >$TMP; elif wget --version &>/dev/null; then wget -O $TMP $URL; else printf >&2 "\n=⇒ Error: failed to detect neither curl nor wget\n"; rm -f $TMP; exit 1; fi || { printf >&2 "\n=⇒ Error: failed to download installation script\n"; rm -f $TMP; exit 2; }; mv -n $TMP ~ && TMP=$HOME/$(basename $TMP) && chmod +x $TMP && $TMP "$@"; RC=$?; rm -f $TMP; ((RC)) && exit 3 || exit 0' bash

Equivalently, the framework can be installed from a local copy of this repository (located not at the installation path) by running this command:

./intervene.sh fmwk-install

The framework installation does nothing too spectacular:

  • Optional dependencies — such as Git — will be offered, unless installed already.

  • This repository will be cloned/downloaded.

  • Optionally, the symlink (di) to the framework’s main script will be created on $PATH.

  • The zero data loss policy will be in effect, as normal.

The script will prompt for every major life decision.

The documentation assumes that the framework has been installed with the optional shortcut command di. Still, all invocations of the di command are equivalent to executing the ./intervene.sh script in the root of the installation directory.

Installation options and overrides

Table 3. Framework installation options and overrides

Prepend on the left

D_DIR=DIRPATH

By default, the framework is installed into the ~/.divine/ directory. This directs to instead install it into the DIRPATH directory.

D_SHCT_NAME=CMD

By default, the shortcut command for the framework is named di. This directs to name it CMD instead.

D_SHCT_DIR=DIRPATH

By default, the shortcut command for the framework is installed into one of the usual /**/bin/ directories on $PATH. This directs to instead install it into the DIRPATH directory.

Append on the right

Prompt options
-y, --yes
-n, --no

Assumes an affirmative (--yes) or negatory (--no) answer to the prompts about installing the framework and all optional parts.

Other options notwithstanding, the --no option guarantees a 'dry run': no changes to the system will be introduced.

Framework prompt options
-d, --fmwk-yes
-D, --fmwk-no

Assumes an affirmative (--yes) or negatory (--no) answer specifically to the prompt about installing the framework.

Other options notwithstanding, the --fmwk-no option guarantees a 'dry run', because no optional parts are installed without the framework itself.

Shortcut prompt options
-s, --shct-yes
-S, --shct-no

Assumes an affirmative (--yes) or negatory (--no) answer specifically to the prompt about installing the shortcut command.

-f, --force

The framework adheres to the non-interference policy.

If the destination path for the framework already exists and is not an empty directory, the installation unconditionally backs off with an alert.

The --force option directs to instead urgently prompt the user and, if given permission, to displace the pre-existing destination to a backup location.

Verbosity options
-v, --verbose (repeatable)
-q, --quiet

Gradually increases (--verbose), or resets to the default minimal level (--quiet), the amount of installation output. This affects the same global verbosity level as is used by the primary routines of the framework.

Note, that no combination of options guarantees an unattended installation of the framework. For example, when installing the optional Git dependency, the underlying package manager may interact with the user in ways that are outside of the script’s control. As established before, Divine.dotfiles is intended as an interactive tool.

Uninstalling the framework

The following single shell command uninstalls the Divine.dotfiles framework:

bash -c 'TMP=$(mktemp); URL=https://raw.github.com/divine-dotfiles/divine-dotfiles/main/lib/uninstall/uninstall.sh; if curl --version &>/dev/null; then curl -fsSL $URL >$TMP; elif wget --version &>/dev/null; then wget -O $TMP $URL; else printf >&2 "\n=⇒ Error: failed to detect neither curl nor wget\n"; rm -f $TMP; exit 1; fi || { printf >&2 "\n=⇒ Error: failed to download uninstallation script\n"; rm -f $TMP; exit 2; }; mv -n $TMP ~ && TMP=$HOME/$(basename $TMP) && chmod +x $TMP && $TMP "$@"; RC=$?;rm -f $TMP; ((RC)) && exit 3 || exit 0' bash

Equivalently, the framework can be uninstalled from a local copy of this repository (located anywhere) by running this command:

./intervene.sh fmwk-uninstall

Also, an existing installation of Divine.dotfiles may be directed to uninstall itself:

di fmwk-uninstall

The framework uninstallation plays out like this:

  • Optional dependencies that have been installed will be offered for removal.

  • The installation directory will be displaced (backed up) according to the zero data loss policy.

  • The Grail directory will remain untouched.

  • The optional symlink (di) will be erased.

The script will prompt for every major life decision.

One thing that framework uninstallation does not do is uninstall deployments. If not needed, any deployments that might have been installed must be removed manually before uninstalling Divine.dotfiles.

Uninstallation options and overrides

Table 4. Framework uninstallation options and overrides

Prepend on the left

D_DIR=DIRPATH

By default, the framework is uninstalled from the ~/.divine/ directory. This directs to instead uninstall it from the DIRPATH directory.

Append on the right

Prompt options
-y, --yes
-n, --no

Assumes an affirmative (--yes) or negatory (--no) answer to the prompts about uninstalling the framework and all optional parts.

Other options notwithstanding, the --no option guarantees a 'dry run': no changes to the system will be introduced.

Framework prompt options
-d, --fmwk-yes
-D, --fmwk-no

Assumes an affirmative (--yes) or negatory (--no) answer specifically to the prompt about uninstalling the framework.

Other options notwithstanding, the --fmwk-no option guarantees a 'dry run', because no optional parts are uninstalled without the framework itself.

Optional dependency prompt options
-u, --util-yes
-U, --util-no

Assumes an affirmative (--yes) or negatory (--no) answer specifically to the prompt about uninstalling the optional dependencies that might have been installed.

-f, --force

The framework adheres to the reversibility policy.

If the script fails to uninstall any of the optional dependencies, the installation unconditionally backs off with an alert.

The --force option directs to instead urgently prompt the user and, if given permission, to ignore the debacle and uninstall the framework anyway.

-o, --obliterate

The framework adheres to the zero data loss policy.

Accordingly, during the uninstallation of the framework, its directory is displaced into a backup location, and the Grail directory is untouched.

The --obliterate option directs to instead erase both the framework directory and the Grail without any backups.

Verbosity options
-v, --verbose (repeatable)
-q, --quiet

Gradually increases (--verbose), or resets to the default minimal level (--quiet), the amount of uninstallation output. This affects the same global verbosity level as is used by the primary routines of the framework.

Note, that no combination of options guarantees an unattended uninstallation of the framework. As established before, Divine.dotfiles is intended as an interactive tool.

Joy ride

The following single shell command provides the introductory experience of the framework and deployments:

All three sub-commands skip the trivial prompts (e.g., 'Are you sure?').

bash -c 'TMP=$(mktemp); URL=https://raw.github.com/divine-dotfiles/divine-dotfiles/main/lib/install/install.sh; if curl --version &>/dev/null; then curl -fsSL $URL >$TMP; elif wget --version &>/dev/null; then wget -O $TMP $URL; else printf >&2 "\n=⇒ Error: failed to detect neither curl nor wget\n"; rm -f $TMP; exit 1; fi || { printf >&2 "\n=⇒ Error: failed to download installation script\n"; rm -f $TMP; exit 2; }; mv -n $TMP ~ && TMP=$HOME/$(basename $TMP) && chmod +x $TMP && $TMP "$@"; RC=$?; rm -f $TMP; ((RC)) && exit 3 || exit 0' bash --yes && ~/.divine/intervene.sh attach essentials --yes && ~/.divine/intervene.sh install --yes

(The undo command is provided in the following section.)

Both the framework and deployments do not overwrite pre-existing files on the system without backing them up. Everything that is backed up is automatically restored upon removal.

After all installations are successful, it might be necessary to reload the shell (or even re-log into the system on macOS). This depends on whether the default shell has been changed.

What it does

Once the bundle is fully installed, and the shell reloaded, voilà:

  • Zsh is the default shell.

  • Zsh is augmented with completions, syntax highlighting, and auto-suggestions.

  • Basic necessities, such as Git, Vim, and GnuPG are available.

  • Both oh-my-zsh and Bash-it frameworks are installed and loaded.

  • A minimalistic theme for both shell frameworks is active.

  • Opinionated configs are plugged in for Git, Vim, Bash, and Zsh.

  • Any pre-existing files that have gotten in the way are safely backed up or re-used.

All of the above is controlled and customized through the key configuration files, which are located at ~/.grail/assets/.

Table 5. Overview of asset directories for the bundle essentials

bash-it/

Custom assets for the Bash-it shell framework.

brewfile/

The Brewfile, maintained on macOS.

config-git/

The global configuration for Git.

config-shell/

The startup scripts (runcoms) for Bash and Zsh.

config-vim/

The global configuration for Vim.

home-dirs/

The file home-dirs.cfg defines a sub-directory tree to be maintained under the home directory.

oh-my-zsh/

Custom assets for the oh-my-zsh shell framework.

portable-bin/

The container for the personal executables (this directory is maintained on the $PATH).

The dagger mark () meaning: in order for the modifications in that asset directory to take effect, the deployment must be (re-)installed.

Undoing the joy ride

The following single shell command methodically undoes the joy ride installation:

All three sub-commands skip the trivial prompts (e.g., 'Are you sure?').

The original state of the system (before the installation) is restored. However, no copies of the framework’s assets are kept.

~/.divine/intervene.sh remove --yes --obliterate && ~/.divine/intervene.sh detach essentials --yes && bash -c 'TMP=$(mktemp); URL=https://raw.github.com/divine-dotfiles/divine-dotfiles/main/lib/uninstall/uninstall.sh; if curl --version &>/dev/null; then curl -fsSL $URL >$TMP; elif wget --version &>/dev/null; then wget -O $TMP $URL; else printf >&2 "\n=⇒ Error: failed to detect neither curl nor wget\n"; rm -f $TMP; exit 1; fi || { printf >&2 "\n=⇒ Error: failed to download uninstallation script\n"; rm -f $TMP; exit 2; }; mv -n $TMP ~ && TMP=$HOME/$(basename $TMP) && chmod +x $TMP && $TMP "$@"; RC=$?;rm -f $TMP; ((RC)) && exit 3 || exit 0' bash --yes --obliterate

After the undo steps have successfully run, there is no trace of Divine.dotfiles on the system. (Sigh.)

Usage

Divine intervention utility di

Divine.dotfiles provides a command line interface via Divine intervention utility di. (Invoking the shortcut di is equivalent to executing the framework’s main script, intervene.sh.)

di [-efhlnoqvwy]… [--version] [-b BUNDLE]… [--] ROUTINE [ARG]…

The first non-option argument, ROUTINE, can be any of the following:

Table 6. List of the intervention utility’s routines

The primary routines, operating on deployments:

  • c|check — checks whether deployments are installed or not;

  • i|install — checks, then, if not installed, installs;

  • r|remove — checks, then, if installed, removes.

The bundle routines, operating on Github repositories:

  • a|attach — imports Github repositories that contain deployments.

  • d|detach — erases previously attached Github repositories.

The routine that wrangles the Grail directory:

  • p|plug — replaces the current Grail directory with the one provided.

The routine that brings what it can up to date:

  • u|update — updates the framework itself, the Grail directory (if it is a cloned repository), and the attached bundles.

The term 'deployments' includes Divinefiles as a special kind.

Options and arguments

The intervention routines, and the Divine.dotfiles overall, use a familiar set of rules for parsing options and arguments:

  • The options and arguments are read left-to-right, word by word, separated by any unescaped whitespace.

    Whenever options override each other, the last option given wins. In the documentation, the overriding options have their descriptions grouped.

  • Most options have single-character and long versions, which are equivalent.

  • The single-character options can be combined (-CHARS).

    Words that start with one hyphen are interpreted as combined options.

  • Words that start with two hyphens (--WORD) are interpreted as long options.

  • The arguments and options can be freely mixed; all words after the optional separator (--) are interpreted as arguments.

  • The two special options take priority over all other arguments/options:

    • -h, --help — outputs the help summary for the intervention utility.

    • --version — prints the version of the framework.

Primary routines (check, install, remove)

The three primary routines somewhat correspond to the three fundamental actions (functions) that the framework recognizes for any deployment:

  • checking whether something is installed;

  • installing it;

  • and removing it.

The correspondence is not strictly one-to-one because all three primary routines do the checking part. The check routine stops there, but the install and remove routines proceed based on the check result.

check routine

The check routine iterates over deployments (and Divinefile packages), ordered by ascending priority (smaller numbers first). The routine checks whether each item is installed or not, then prints the appropriate plaque.

$ di [-efhnoqvwy] [-b BUNDLE]… [--] c|check [NAME]…

The meaning of the optional NAME arguments and the options is near identical for all three primary routines. Their description is grouped below.

For each deployment, the check routine calls the d_dpl_check primary function, and deduces the current status from the return code.

install routine

The install routine iterates over deployments (and Divinefile packages), ordered by ascending priority (smaller numbers first). The routine checks whether each item is installed or not. If the item is not (fully) installed, the routine installs it, and prints the appropriate plaque.

$ di [-efhnoqvwy] [-b BUNDLE]… [--] i|install [NAME]…

The meaning of the optional NAME arguments and the options is near identical for all three primary routines. Their description is grouped below.

For each deployment, the install routine calls the d_dpl_check primary function, and deduces the current status from the return code. Depending on that, the routine either returns immediately, or calls the d_dpl_install primary function. The return code of the latter is taken to indicate whether the installation succeeded.

remove routine

The remove routine iterates over deployments (and Divinefile packages), ordered by descending priority (larger numbers first). The routine checks whether each item is installed or not. If the item is (at least in some part) previously installed, the routine removes it, and prints the appropriate plaque.

$ di [-efhnoqvwy] [-b BUNDLE]… [--] r|remove [NAME]…

The meaning of the optional NAME arguments and the options is near identical for all three primary routines. Their description is grouped below.

For each deployment, the remove routine calls the d_dpl_check primary function, and deduces the current status from the return code. Depending on that, the routine either returns immediately, or calls the d_dpl_remove primary function. The return code of the latter is taken to indicate whether the removal succeeded.

Specifying deployments

For the optional NAME arguments, the following (case-insensitive) values are accepted:

The primary routines filter the deployments according to these rules:

  • Without any NAME arguments, all deployments are processed.

  • With at least one NAME argument, some form of filtering is applied:

    • In the normal filtering, a deployment is processed if and only if it is requested by name or by name of its single-digit group.

    • The --except option makes the filtering inverted: all deployments are processed, unless requested by name or by name of its single-digit group.

      Note, that without any NAME arguments, the --except option is a no-opt.

Dangerous deployments are treated specially:

  • A dangerous deployment is ignored by both filtering modes, unless it is requested by name in the normal filtering.

    Note, that requesting by name of the single-digit group does not work for dangerous deployments.

  • The --with-! option prevents any special treatment of dangerous deployments.

Deployments are retrieved from two directories (at any depth):

  • The directory for user’s deployments: ~/.grail/dpls/.

  • The directory for attached bundles of deployments: state/bundles/.

The search can be narrowed down to particular bundles of deployments by including any number of the --bundle options.

Primary routine options

The run-time state of all options can be inspected within the deployment code through their corresponding read-only global variables.

The Divine bundles of deployments follow the given interpretation of the options, as well as the framework’s design in general. A 'good' deployment is expected to follow suit.

Table 7. Primary routine options

Prompt options
-y, --yes
-n, --no

Assumes an affirmative (--yes) or negatory (--no) answer to the non-urgent framework prompts, e.g.:

  • A confirmation before sourcing (reading and interpreting) each deployment script.

  • A confirmation before installing an optional framework dependency.

Examples of the urgent prompts, which are not affected by the prompt options:

  • A confirmation before undertaking a forced action.

  • Any prompt in the user-defined deployment code, unless it is specifically made to honor this option.

Other options notwithstanding, the --no option guarantees a 'dry run': no user code will be sourced, and no changes to the system will be introduced.

The status of the prompt options is reflected in the value of the $D__OPT_ANSWER variable:

  • true — the --yes option is active;

  • false — the --no option is active;

  • empty — none of the prompt options have been given.

-o, --obliterate

The framework adheres to the zero data loss policy.

Whenever a built-in framework mechanism is not sure whether a particular piece of data is recoverable, it chooses to displace it to a backup location instead of deleting it.

The --obliterate option directs to instead erase such data.

The presence of the --obliterate option is reflected in the value of the $D__OPT_OBLITERATE variable:

  • true — the option is given;

  • false — the option is not given.

-f, --force

The framework adheres to the non-interference policy.

During the install and remove routines, if the check code speaks to a clash with something done by the user or the operating system (e.g., a previously installed file is nowhere to be found), the framework unconditionally backs off with an alert.

The --force option directs to instead urgently prompt the user and, if given permission, carry out the installation/removal anyway.

The presence of the --force option is reflected in the value of the $D__OPT_FORCE variable:

  • true — the option is given;

  • false — the option is not given.

-b BUNDLE, --bundle BUNDLE (repeatable)

If at least one such option is included, the search for deployments will be limited to the given attached bundles of deployments.

The same values are accepted for the BUNDLE arguments as are for the REPO arguments during the bundle routines.

The list of requested bundles (if any) is reflected in the items of the ${D__REQ_BUNDLES[@]} array.

-e, --except

The primary routines interpret the optional NAME arguments as a list of deployments to process. The --except option directs to instead interpret the arguments as the list of deployments to exclude from processing.

The presence of the --except option is reflected in the value of the $D__OPT_INVERSE variable:

  • true — the option is given;

  • false — the option is not given.

-w, --with-!

The primary routines interpret the dangerous deployments (marked with the ! flag) specially by making them harder to accidentally process. The --with-! option directs to instead give them no special treatment at all.

The presence of the --with-! option is reflected in the value of the $D__OPT_EXCLAM variable:

  • true — the option is given;

  • false — the option is not given.

Verbosity options
-v, --verbose (repeatable)
-q, --quiet

Gradually increases (--verbose), or resets to the default minimal level (--quiet), the amount of framework output.

Every instance of the --verbose option increments by one the global verbosity level of the framework. On the other hand, the framework’s printing functions have the comparable quiet level. For a message to be printed, the global verbosity level must be greater than or equal to that message’s quiet level.

The --quiet option reverts the global verbosity level to its default value of zero.

The amount of output per the verbosity level can be described as such:

  • -q — prints just the essentials and the critical alerts;

  • -v — allows some non-critical alerts here and there;

  • -vv — also, announces the internal context switches;

  • -vvv — enters into the serious debugging mode;

  • -vvvv — just floods the user with everything it has.

    Further verbosity levels are yet unused by the framework.

The global verbosity level is reflected in the value of the $D__OPT_VERBOSITY variable, which is a non-negative integer.

Bundle routines (attach, detach)

The framework treats any Github repository containing deployments as bundles of deployments.

When a bundle is attached to the particular Grail directory, its address is stored in the Grail stash, while a copy of the repository is pulled into the state directory. This way, the attached deployments are treated as if they are in the Grail, but the directory itself is not unnecessarily bloated.

On every invocation, the intervention utility synchronizes the stashed list of attached bundles with the copies in the state directory. Thus, transfering the Grail directory alone to another machine is enough to fully re-create the set of deployments.

The two bundle routines do what one might expect:

attach routine

The attach routine takes any number of repository handles, ensures that they correspond to existing Github repositories containing deployments, and attaches each that does.

$ di [-yn]… [--] a|attach [REPO]…

The meaning of the optional REPO arguments and the options is identical for both bundle routines. Their description is grouped below.

detach routine

The detach routine takes any number of repository handles, ensures that they are currently attached to the Grail directory, and detaches those that are. Both the

$ di [-yn]… [--] d|detach [REPO]…

The meaning of the optional REPO arguments and the options is identical for both bundle routines. Their description is grouped below.

Detaching a bundle deletes the copy of its repository, as well as the stash record. However, it is left to the user to:

  • Uninstall the deployments.

    (If a lapse occurred, re-attaching and uninstalling should work fine.)

  • Remove any assets that might have been copied into the Grail's asset directory.

Specifying bundles

For the optional REPO arguments, the following (case-insensitive) values are accepted:

  • A Github repository in the form: username/repository.

  • Specifically for the Divine bundles, a shorthand is accepted:

    REPO  =>  divine-bundles/REPO

    (REPO must match the RegEx pattern ^[0-9A-Za-z_.-]+$.)

Bundle routine options

Table 8. Bundle routine options

Prompt options
-y, --yes
-n, --no

Assumes an affirmative (--yes) or negatory (--no) answer to the non-urgent framework prompts, e.g.:

  • A confirmation before attaching or detaching a bundle.

  • A confirmation before installing an optional framework dependency.

Other options notwithstanding, the --no option guarantees a 'dry run': no changes to the system will be introduced.

Verbosity options
-v, --verbose (repeatable)
-q, --quiet

Gradually increases (--verbose), or resets to the default minimal level (--quiet), the amount of framework output. This affects the same global verbosity level as is used by the primary routines of the framework.

plug routine

The Grail directory is the central hub for all user content. As such, it is the obvious target for version controlling and syncing.

The plug routine, unsurprisingly, imports an externally saved Grail directory, replacing the currently existing one.

$ di [-ynl] [--] p|plug ADDRESS

For the ADDRESS argument, the following (case-insensitive) values are accepted:

  • A Github repository in the form: username/repository.

  • A path to a Git repository.

  • A path to a local directory.

The framework iterates over possible interpretations of the argument and prompts the user for confirmation.

The repositories are cloned, the directories are copied. The pre-existing Grail directory is backed up in-place by appending the .bak suffix.

plug routine options

Table 9. plug routine options

Prompt options
-y, --yes
-n, --no

Assumes an affirmative (--yes) or negatory (--no) answer to the non-urgent framework prompts, e.g.:

  • A confirmation before interpreting the ADDRESS argument in a particular way.

  • A confirmation before installing an optional framework dependency.

With the --yes option active, the first viable interpretation of the ADDRESS argument will be silently settled upon.

Other options notwithstanding, the --no option guarantees a 'dry run': no changes to the system will be introduced.

-o, --obliterate

The framework adheres to the zero data loss policy.

Accordingly, during the plug routine the pre-existing Grail directory is displaced into a backup location.

The --obliterate option directs to instead erase the pre-existing Grail.

-l, --link

Normally, the plug routine retrieves a copy of the Grail at the given ADDRESS.

The --link option directs to instead create a symlink to it. Consequently, when the --link option is active, the ADDRESS is interpreted only as a local directory.

Verbosity options
-v, --verbose (repeatable)
-q, --quiet

Gradually increases (--verbose), or resets to the default minimal level (--quiet), the amount of framework output. This affects the same global verbosity level as is used by the primary routines of the framework.

update routine

There are three parts of a Divine.dotfiles installation that are potentially cloned Git repositories:

  • The framework itself.

  • The Grail directory (if plugged from a repository).

  • The attached bundles of deployments.

The update routine is a convenient tool that pulls the latest updates from the remote master branches of such repositories.

$ di [-yn] [--] u|update [f|framework] [g|grail] [b|bundles]

The update routine is three-pronged, and the user is free to choose which prongs to engage by providing or not providing arguments:

  • f|framework updates the framework.

  • g|grail — updates the cloned Grail directory.

  • b|bundles — updates all attached bundles of deployments.

  • Without any arguments, all three types of updates are performed.

update routine options

Table 10. update routine options

Prompt options
-y, --yes
-n, --no

Assumes an affirmative (--yes) or negatory (--no) answer to the non-urgent framework prompts, e.g.:

  • A confirmation before pulling from a remote repository.

  • A confirmation before installing an optional framework dependency.

Other options notwithstanding, the --no option guarantees a 'dry run': no changes to the system will be introduced.

-b BUNDLE, --bundle BUNDLE (repeatable)

If at least one such option is included:

  • the presence of bundles argument is implicitly assumed;

  • only the given bundles (if currently attached) are updated.

The same values are accepted for the BUNDLE arguments as are for the REPO arguments during the bundle routines.

Verbosity options
-v, --verbose (repeatable)
-q, --quiet

Gradually increases (--verbose), or resets to the default minimal level (--quiet), the amount of framework output. This affects the same global verbosity level as is used by the primary routines of the framework.

Deployments

A Divine.dotfiles deployment is a Bash script with the file name consisting of a non-empty name and the .dpl.sh suffix. No other parts of a deployment are mandatory.

To be picked up by the framework, the deployments must be located at any depth under the two recognized deployment locations:

  • ~/.grail/dpls/ — the user’s deployments;

  • state/bundles/ — the attached bundles of deployments.

Deployment structure

The minimal valid deployment is an empty file. As such, it does nothing but appear in the framework output.

Deployments are written in Bash syntax, with some syntax limitations on the metadata. Each deployment is sourced by the Bash interpreter no more than once per primary routine.

To be useful, a deployment may contain:

It is highly recommended to not include any non-trivial Bash code outside of functions. Nothing will prevent things from going off the rails. There are no safety nets. Unless the intention is very well thought-out, Divine.dotfiles is only useful while the guidelines are followed.

Primary functions (d_dpl_check, d_dpl_install, d_dpl_remove)

The primary functions, or primaries, correspond to the three fundamental actions performed by a deployment:

  • d_dpl_check — checks whether the deployment is installed or not.

  • d_dpl_install — installs the deployment.

  • d_dpl_remove — removes (reverses the previous installation of) the deployment.

The primary functions have the following ways of interacting with the framework:

  • To 'talk' to the framework:

    • The return codes are the main vehicles of indicating status.

    • The add-statuses (specially named global variables) are available to tweak the behavior in some places.

  • To 'hear' from the framework:

    • The indicator variables are populated by the framework with the relevant run-time data.

Primary function d_dpl_check

If this function is implemented, it will be called:

  • During the check routine — to determine the status and to show the relevant output.

  • During the install routine — to determine whether the installation is warranted.

  • During the remove routine — to determine whether the removal is warranted.

The return code of the d_dpl_check function determines the current status of the deployment. (The following summary of the recognized codes is expanded upon in the relevant section.)

  • Basic codes:

    • 0 — 'Truly unknown'

      This code is assumed if the d_dpl_check function is not implemented.

    • 1 — 'Fully installed'.

    • 2 — 'Fully not installed'.

    • 3 — 'Irrelevant or invalid'.

  • Extended codes:

    • 4 — 'Partly installed'.

    • 5 — 'Likely installed (unknown)'.

    • 6 — 'Manually removed (tinkered with)'.

    • 7 — 'Fully installed (by user or OS)'.

    • 8 — 'Partly installed (by user or OS)'.

    • 9 — 'Likely not installed (unknown)'.

Some additional instructions can be passed to the framework via the add-status variables and the deployment metadata.

Primary function d_dpl_install

If this function is implemented, it will be called:

  • During the install routine — to install the deployment.

The return code of the d_dpl_install function describes the outcome of the installation. (The following summary of the recognized codes is expanded upon in the relevant section.)

  • 0 — 'Successfully installed'

    This code is assumed if the d_dpl_install function is not implemented.

  • 1 — 'Failed to install'.

  • 2 — 'Refused to install'.

  • 3 — 'Partly installed'.

Some additional instructions can be passed to the framework via the add-status variables and the deployment metadata.

Primary function d_dpl_remove

If this function is implemented, it will be called:

  • During the remove routine — to remove the deployment.

The return code of the d_dpl_remove function describes the outcome of the removal. (The following summary of the recognized codes is expanded upon in the relevant section.)

  • 0 — 'Successfully removed'

    This code is assumed if the d_dpl_remove function is not implemented.

  • 1 — 'Failed to remove'.

  • 2 — 'Refused to remove'.

  • 3 — 'Partly removed'.

Some additional instructions can be passed to the framework via the add-status variables and the deployment metadata.

Deployment metadata

Deployment metadata pose as definitions of Bash global variables and alter deployment’s appearance and behavior. In reality, the metadata 'assignments' are read from the file before the Bash interpreter sources it. The metadata are all optional; they may be given in any order and disjointly. However, the metadata must precede all other code of the deployment script.

  • D_DPL_NAME=NAME

    The explicit name for the deployment. The implicit fallback name is the name of the deployment file sans the .dpl.sh suffix.

  • D_DPL_DESC="DESCRIPTION TEXT"

    The one-line description of the deployment. For the user’s eyes only.

  • D_DPL_PRIORITY=PRIORITY

    The priority of the deployment (a non-negative integer).

  • D_DPL_FLAGS=FLAGS

    The single-character flags that cause special treatment.

  • D_DPL_WARNING='WARNING TEXT'

    The one-line cautionary message about this deployment. This message is printed to the user if and only if the always-prompt flag causes the appearance of an urgent prompt.

  • D_DPL_OS=( OS LIST )

    The value of this pseudo-variable defines the list of operating systems that the deployment supports. On non-supported OS’s, the deployment is ignored completely.

Below is an example of metadata in the head of a deployment script:

D_DPL_NAME=example
D_DPL_DESC='An example deployment'
D_DPL_PRIORITY=777
D_DPL_FLAGS=ci!89
D_DPL_WARNING="A warning message"

The following syntax limitations are imposed upon metadata:

  • As mentioned above, the metadata must precede all other non-whitespace, non-commented lines of the deployment script.

  • No more than one 'assignment' must be written per line, without line continuation.

  • No Bash substitutions or comments are allowed.

  • In keeping with the Bash syntax, no whitespace is allowed around the =.

  • A pair of matching quotes around the value is allowed. Such pair of quotes is stripped in processing.

Deployment name and description

D_DPL_NAME=example
D_DPL_DESC='An example deployment'

While the description is mostly cosmetic, the name of a deployment is very important. The name is the single unique identifier of every deployment. If the deployment name is not provided explicitly, the name of the script file is used instead, sans the .dpl.sh suffix.

Deployment names are case insensitive.

The following restrictions are imposed upon the deployment names:

  • A deployment may not be named divinefile, dfile, or df.

  • A deployment name may not be a single digit or a single ! symbol.

  • No two deployments across the deployment directories may share a name.

If any of the naming rules is broken, the framework halts with an alert, even before a primary routine starts.

Deployment priority

D_DPL_PRIORITY=777

The priority is the way to order the deployments for processing:

  • The check and install routines order the deployments by ascending priority (smaller numbers first).

  • The remove routine orders the deployments by descending priority (larger numbers first).

  • The order of deployments with the same priority is undefined.

The priority must be a non-negative integer, otherwise it falls back to the default value of 4096.

Deployment flags

D_DPL_FLAGS=ci!89

The flags alter some of the framework’s behavior toward the deployment.

  • A flag is a single non-whitespace character.

  • Any number of flags can be combined in any order.

  • Repeating a flag does not bear any additional significance.

  • There is no way to unset a flag, apart from not setting it.

  • Unsupported flags are silently ignored.

Below is the exhaustive rundown of supported flags and their effects.

Table 11. List of supported deployment flags

[0-9] (any single digit)

Assigns the deployment to one of the ten single-digit groups. When invoking a primary routine, a group of deployments may be referred to by that group’s digit, instead of listing the deployment names.

! (an exclamation mark)

Marks the deployment as dangerous. The framework ignores dangerous deployments, unless explicitly told not to.

[cira] (any of the four lowercase letters)

These flags engage the always-prompt mode for particular primary routines. An urgent prompt will appear on the chosen routines, before the deployment script is sourced.

  • c — always prompt during the check routine.

  • i — always prompt during the install routine.

  • r — always prompt during the remove routine.

  • aall of the avove.

Deployment warning

D_DPL_WARNING="Warning for 'urgent' prompts forced by a flag"

If a deployment has a warning, and the always-prompt mode is engaged, then the warning is printed alongside the urgent prompt.

Deployment OS’s

# The deployment supports only the listed OS's:
  D_DPL_OS=( bsd macos )
  # or
  D_DPL_OS='debian fedora ubuntu'

# or

# The deployment supports all OS's except those listed:
  D_DPL_OS=( ! "linux" 'wsl' )
  # or
  D_DPL_OS="! cygwin msys solaris"

There are two equivalent syntaxes acceptable for the D_DPL_OS pseudo-variable: array and string. The operating systems are given as a whitespace-separated list. In the array syntax, the individual OS names may be quoted.

The entire list is negated by including the ! symbol as the first non-whitespace character. An empty list, negated or not, greenlights all OS’s. The equivalent keywords any and all are also reserved to denote any OS.

The names of the OS’s are matched against the values of the DOS_FAMILY and DOS_DISTRO indicators. A match against any of the two is sufficient.

Return codes

The return codes are the main vehicle for communicating to the framework the result of running a primary function.

The supported return codes have semantic meanings, which affect the human-readable output. In the case of the d_dpl_check function, the check code also determines whether the install and remove routines proceed with their task. Guidelines are provided for how to interpret the various check codes in the user-defined deployment code.

Finally, most of the extended check codes represent an unusual situation which causes the framework to unconditionally back off from the deployment, with an alert. This is in keeping with the framework’s non-interference policy. The --force option may be used to compel the framework.

check codes

The four basic check codes are the bread & butter that can satisfy most deployment needs. A set of extended check codes is provided for those deployments that use the stashing system to 'remember' the status of the installation between interventions.

Table 12. Supported return codes of the d_dpl_check function

check code

Meaning

Sub-statuses

Actions during the primary routines

Relevant?

Stash record?

Appears installed?

install

remove

Regular

Forced

Regular

Forced

Basic codes

0

'Truly unknown'

Yes

unused

unknown

Push backup; install

Uninstall; pop backup

1

'Fully installed'

Yes

Yes / unused

Yes

blocked

Push backup; install anew

—or—

Bring up to date

Uninstall; pop backup; clear stash

2

'Fully not installed'

Yes

No / unused

No

Push backup; install; set stash

blocked

Pop backup

3

'Irrelevant or invalid'

No

n/a

blocked

Extended codes

4

'Partly installed'

Yes

Yes / unused

Partly

Make us whole

Push backup; install anew

Uninstall; pop backup; clear stash

5

'Likely installed (unknown)'

Yes

Yes

unknown

blocked

Push backup; install anew

—or—

Bring up to date

blocked

Uninstall; pop backup; clear stash

6

'Manually removed (tinkered with)'

Yes

Yes

No

blocked

Push backup; install anew

blocked

Clear stash

7

'Fully installed (by user or OS)'

Yes

No

Yes

blocked

Push backup; install anew; set stash

blocked

Uninstall; pop backup

8

'Partly installed (by user or OS)'

Yes

No

Partly

Make whole; set stash

Push backup; install anew; set stash

blocked

Uninstall; pop backup

9

'Likely not installed (unknown)'

Yes

No

unknown

blocked

Push backup; install; set stash

blocked

Pop backup

install codes

The install codes are much less diversified than the check codes because no major internal decisions are made based on the result of the installation.

Table 13. Supported return codes of the d_dpl_install function

install code

Meaning

Elaboration

0

'Successfully installed'

The function has run without any errors.

1

'Failed to install'

There has been at least one error, which potentially created an inconsistent state.

2

'Refused to install'

The function has returned before proceeding to the installation proper.

3

'Partly installed'

The function has returned midway (because of an error), but has avoided creating an inconsistent state.

remove codes

The remove codes are much less diversified than the check codes because no major internal decisions are made based on the result of the removal.

Table 14. Supported return codes of the d_dpl_remove function

remove code

Meaning

Elaboration

0

'Successfully removed'

The function has run without any errors.

1

'Failed to remove'

There has been at least one error, which potentially created an inconsistent state.

2

'Refused to remove'

The function has returned before proceeding to the removeation proper.

3

'Partly removed'

The function has returned midway (because of an error), but has avoided creating an inconsistent state.

Add-statuses

The add-statuses are the specially named global variables. The framework clears the add-statuses before running a primary function, and after running it — checks if the add-statuses contain (supported) values. The value assigned to an add-status changes the behavior of the framework.

Table 15. Supported add-statuses

Add-statuses in primary functions

D_ADDST_PROMPT

If set to true, forces an urgent prompt before installation/removal.

Naturally, this add-status only makes sense when set in the d_dpl_check function during the install or remove routines.

D_ADDST_HALT

If set to true, forces halting of the current primary routine. No further deployments will be processed.

Output add-statuses
D_ADDST_ATTENTION, D_ADDST_HELP,
D_ADDST_WARNING, D_ADDST_CRITICAL

The following output add-statuses may be set to a string or array thereof. If set, their messages will be printed to the user with appropriate styling.

  • D_ADDST_ATTENTION — non-critical alerts to the user.

  • D_ADDST_HELP — calls for user’s involvement (e.g., requests to reboot the machine).

  • D_ADDST_WARNING — grave warnings to the user.

  • D_ADDST_CRITICAL — critical failures.

Other than printing the styled message, these add-statuses do nothing.

Indicator variables

An indicator variable (or simply an indicator) is a global variable that is populated by the framework at run-time with the intention of providing potentially useful information to the deployment code.

The indicator variables are read-only in spirit. However, due to practical limitations, they are not always protected from writing using the Bash’s readonly mechanism. Please, enjoy responsibly.

Table 16. List of indicator variables

Deployment check code indicator
D__DPL_CHECK_CODE

This indicator contains the integer check code returned by the d_dpl_check primary function. Naturally, this indicator is only available to the d_dpl_install and d_dpl_remove primary functions.

D__DPL_IS_FORCED

In the install and remove primary functions, this indicator reflects whether the current deployment is being forced. If set to true, this indicator tells that the installation/removal would not have been run if the --force option weren’t provided.

Possible values: true / false.

Current OS indicators
D__OS_FAMILY
D__OS_DISTRO
D__OS_PKGMGR

These indicators describe the current operating system as detected by the built-in framework mechanisms. All are declared readonly.

  • D__OS_FAMILY — the broad description of the current OS family.

    The framework does not start up unless it detects one of the following OS families:

    It should be noted, that linux and wsl are separate entries.

  • D__OS_DISTRO — the best guess on the name of the current OS distribution:

    • debian

    • fedora

    • freebsd

    • macos

    • ubuntu

    • empty — failed to reliably detect a supported distribution

    The system requirements always show the list of supported operating systems and ways to expand it.

  • D__OS_PKGMGR — the name of the supported package manager on the current OS:

    • apt-get

    • brew

    • dnf

    • pkg

    • yum

    • empty — failed to reliably detect a supported package manager

    Whenever this variable is not empty, the built-in package manager wrapper, d__os_pkgmgr, is available.

Special directory indicators
D__DPL_DIR
D__DPL_ASSET_DIR
D__DPL_BACKUP_DIR

These indicators contain the absolute paths to the deployment’s special directories. All are declared readonly.

  • D__DPL_DIR — the directory containing the deployment file.

  • D__DPL_ASSET_DIR — the directory generated for the deployment’s assets.

    Located at: ~/.grail/assets/DPL-NAME/

  • D__DPL_BACKUP_DIR — the directory generated for the deployment’s backups.

    Located at: state/backups/DPL-NAME/

Special path indicators
D__DPL_SH_PATH
D__DPL_MNF_PATH
D__DPL_QUE_PATH

These indicators contain the absolute paths to the deployment’s special files. All are declared readonly.

  • D__DPL_SH_PATH — the deployment file itself.

  • D__DPL_MNF_PATH — the path where the deployment’s asset manifest will be looked up.

    The same as the deployment’s filepath, with the .dpl.sh suffix changed to .dpl.mnf.

  • D__DPL_QUE_PATH — the path where the deployment’s queue manifest will be looked up.

    The same as the deployment’s filepath, with the .dpl.sh suffix changed to .dpl.que.

    This path in particular can be overridden with an add-status. However, the override will not be reflected in the indicator.

Deployment metadata indicators
D__DPL_NAME, D__DPL_DESC, D__DPL_PRIORITY,
D__DPL_FLAGS, D__DPL_WARNING

Since the deployment metadata 'assignments' are technically Bash assignments too, they are available to the deployment code.

Option indicators
D__OPT_ANSWER, D__OPT_OBLITERATE, D__OPT_FORCE,
D__OPT_INVERSE, D__OPT_EXCLAM, D__OPT_VERBOSITY

The option indicators reflect the run-time state of the options, provided to the primary routine.

The option indicators are declared readonly with the following possible values:

  • D__OPT_ANSWER — true / false / empty.

  • D__OPT_OBLITERATE — true / false.

  • D__OPT_FORCE — true / false.

  • D__OPT_INVERSE — true / false.

  • D__OPT_EXCLAM — true / false.

  • D__OPT_VERBOSITY — non-negative integer.

Request indicators
D__REQ_ROUTINE, D__REQ_ARGS, D__REQ_GROUPS,
D__REQ_BUNDLES, D__REQ_PKGS

The request indicators reflect the parameters of the current request to the primary routine.

The request indicators are declared readonly with the following possible values:

  • D__REQ_ROUTINE — check / install / remove.

  • D__REQ_ARGS — array of non-option non-group arguments.

  • D__REQ_GROUPS — array of group arguments.

  • D__REQ_BUNDLES — array of bundles requested.

  • D__REQ_PKGS — true / false (whether the Divinefiles are requested).

Manifests

Divine.dotfiles introduces a simple markup language for special files called manifests.

There are three types of special files that are manifests:

While they differ in purpose and supported features, all types of manifests share basic syntax and are internally parsed by the same engine.

Manifest syntax

Manifests are processed in terms of lines. A simplest line represents an entry of some kind.

The whitespace rules are fairly permissive. Any amount of leading and trailing whitespace is allowed and ignored. Within an entry, the whitespace is preserved.

Example of manifest with four entries
entry1
entry2
entry with whitespace
  indented entry will not include indentation

Key-values

Whenever a line starts with an opening parenthesis ( and contains a closing one ), what’s between them is interpreted as a key-value pair. There may be more than one key-value per line. The key-values are used to qualify entries and provide additional information.

A key-value is separated into key and value by the first occurrence of the : symbol (colon).

Key-values may:

  • occupy their own line;

  • precede an entry.

Key-values that occupy their own line come into effect for the rest of the document, or until overridden. Key-values that precede an entry affect only that entry.

Example of key-values in manifest
entry1                  # Regular entry
(color: red) entry2     # Set color to red for this entry only

(color: blue)           # Set color to blue henceforth

entry3                  # Color is blue
(color: green) entry4   # Color is green (overridden)
entry5                  # Color is blue

(color:)                # Unset color henceforth

entry6                  # No color
entry7                  # No color

The two keys — os and flags — are universal to all types of manifests, and are described below. Particular kinds of manifests support additional keys.

Key-value os

The key os makes entries specific to particular operating systems. Multiple OS names may be given by separating them with whitespace. The entire list of OS’s may be negated by prepending it with the ! symbol.

Example of os key-values in manifest
(os: debian)          entry1    # Relevant only on Debian

(os: macos bsd)       entry2    # Relevant only on macOS or BSD

(os: ! linux wsl)     entry3    # Relevant everywhere except Linux or WSL

(os: all)             entry4    ## Keywords 'all'/'any' are reserved to denote
                                #. any OS. This is synonymous to empty list.

The OS names are matched against the $D__OS_FAMILY and $D__OS_DISTRO variables. A match against any of the two is sufficient.

Key-value flags

The key flags adds a string of single-character flags to an entry.

A shorthand is provided: whenever a key-value does not contain the : separator (i.e., there is no key), the flags key is assumed.

Flags may be appended to those currently in effect (instead of replacing them) by prepending the value with the + symbol.

Example of flags key-values in manifest
(flags: i!0)  entry1    # Flags: i, !, 0

(flags: a)
              entry2    # Flags: a
(+b)
(flags: +c)   entry3    # Flags: a, b, c
              entry4    # Flags: a, b
(flags: d)    entry5    # Flags: d
              entry6    # Flags: a, b

Comments, line continuation, and escaping

The hash/pound symbol (#) comments out the rest of the line.

A line may be 'glued' to the next by terminating it with a backslash (\). Whitespace and comments are allowed to follow the backslash.

Example of line continuation in manifests
(os: fedora)  \   ## This is a single logical line
lengthy entry \   #. spanning three physical lines
text              #. (yes, even with comments attached like this)

The escaping rules are as follows:

  • To start an entry with a literal opening parenthesis (, prepend it with a backslash \.

    One and only one backslash is always removed from the left edge of an entry.

  • To use a literal closing parenthesis ) within a key-value, prepend it with a backslash.

  • To use a literal hash/pound symbol # anywhere, prepend it with a backslash.

  • To end a line with a literal backslash \, double every literal backslash at the line’s right edge.

    An odd number of backslashes at the right edge of a line will be reduced to a single backslash and will result in line continuation.

Assets

Deployment assets are any files that are associated with the deployment, but are not part of its deployment script. Divine.dotfiles provides a way to separate the static deployment logic from the dynamic deployment assets.

Every deployment is alotted a designated asset directory at ~/.grail/assets/DPL-NAME/. Keeping the deployment assets in their separate directory within the Grail provides the following advantages:

  • The assets — e.g., symlinked configuration files — are in one place, for the user to inspect and modify.

  • The assets can be version-controlled and synchronized independently from their deployment scripts.

Asset manifests

The asset manifests are used to:

The asset manifest is looked up at the path of the deployment file, with the .dpl.sh suffix exchanged for .dpl.mnf.

An asset manifest entry is a relative path to a file. (In regards to assets, the term 'file' includes directories.) Two kinds of relative paths are accepted: concrete paths and RegEx patterns. Leading and trailing slashes are always disregarded. The relative path is resolved from:

  • the deployment directory (to locate the initial versions possibly provided with the deployment);

  • the deployment’s asset directory (to locate the user’s current versions).

Asset manifest usage

Processing of the asset manifests occurs:

The asset manifests follow the general manifest syntax, which provides the OS recognition and the flags.

What is being done for the catalogued assets is largely determined by their flags:

Table 17. List of asset manifest flags
Behavior without the flag (default) Asset flag Behavior with the flag

The entry is interpreted as a concrete path to a single asset.

r

RegEx

The entry is interpreted as a RegEx pattern (see note below) that can match any number of assets.

Some version of the asset must be provided by the deployment’s author in the deployment directory. If the entry is a RegEx pattern, it must have at least one matching asset. Failing that, the entire deployment is not processed at all.

o

optional

The asset entry is considered optional: its provision by the author is not enforced.

The matching asset(s) within the deployment directory are copied into the asset directory.

d

dpl-dir-only

This asset entry does not leave the deployment directory. Matching asset(s) are not copied anywhere, and are pushed onto the asset arrays from their original locations. This provides a way to conceal assets from user’s view.

The assets already in the user’s Grail are not overwritten under any circumstances.

f

force-copy

The framework ensures that an exact copy of the provided version of an asset is present in the user’s Grail. If a differing version is found there, it is backed up, and overwritten.

This flag is useful for supplying and updating READMEs. Whenever a file matches the RegEx pattern README(\.[a-z]+)? (case-sensitive), the framework does not create a backup.

This flag should be used sparingly!

Paths to matching assets are pushed onto the asset arrays for further usage.

n

no-queue

Paths to matching assets are not pushed onto the asset arrays.

Together with the d flag, this will cause the asset to be completely ignored.

All matching assets in the asset directory are pushed onto the asset arrays. This will include any matching assets added by the user manually.

Irrelevant when the d flag is in effect.

p

provided-only

The asset entry is considered limited to those matching assets, for which the author has provided the initial versions.

Irrelevant when the d flag is in effect.

On top of the flags, the following key-values are recognized in asset manifests:

Table 18. List of asset manifest key-values

(prefix: SUBPATH)

The SUBPATH is implicitly prepended to the asset entries when looking up the assets in the deployment directory, but not in the asset directory. Leading and trailing slashes are ignored.

(queue: split)

This key-value does not affect the actual asset entries, nor does it support any values other than the pre-defined word split.

The (queue: split) key-value splits the queue, which is auto-generated from the asset paths. Such splits occur at the exact positions where the key-value is encountered.

The RegEx patterns are interpreted by the find utility using the POSIX Extended Regular Expressions dialect. The provided pattern is inserted into a larger one, e.g.:

find -E . -regex "^\./${PATTERN}$"

Consequently, the patterns should not include the ^ and $ meta-characters.

The order of entries in the asset manifest is guaranteed to correspond to the order of elements in the resulting asset arrays. However, the order of assets that match a single RegEx entry is not guaranteed.

The relative paths from a manifest are simply appended to their respective parent directories, so the paths like . or .. or ../.. will work.

Example of asset manifest
file1.txt           ## These files will be copied from the deployment directory
file2.txt           #. into the root of the asset directory.

(r) configs/\       ## The matching '*.cfg' files will be copied with their
[a-z]+\.cfg         #. parent 'configs/' directory preserved on both ends.

(prefix: images)
img1.jpg            ## These files will be copied from the 'images/' directory
img2.jpg            #. into the root of the asset directory.

(prefix:)
(d) sys.f           ## These files will not be copied, but their paths will be
(d) sys.d           #. pushed onto the asset arrays

Asset arrays

Whenever the framework processes an existing asset manifest, it automatically clears and then populates two global arrays:

  • D_QUEUE_ASSETS — absolute paths to the assets within the deployment’s asset directory.

  • D_QUEUE_MAIN — for each absolute path in the previous array, this one will contain its relative version.

These arrays coincide with the arrays used by the queues, especially the link-queue and copy-queue variants. It makes sense, because the assets are the perfect candidates for sequential processing. The deployment is, of course, free to override these automatically populated arrays.

Divinefiles

A Divinefile is a special kind of deployment. Its purpose is akin to that of the Brewfile or the Gemfile. Quite simply, a Divinefile is a manifest of packages to be maintained using the supported system package manager.

  • A Divinefile must be named, well, Divinefile.

  • There can absolutely be more than one — their contents are effectively merged.

  • The framework picks up every Divinefile located at any depth under two recognized deployment directories:

    • ~/.grail/dpls/ — the user’s Divinefiles.

    • state/bundles/ — the attached third-party Divinefiles.

  • The Divinefiles collectively act as a deployment.

Divinefile usage

The Divinefiles are automatically picked up by the framework along with the other deployments. Divinefiles are processed in their merged entirety or not processed at all.

The Divinefiles are referred to with synonyms: divinefile, dfile, or df. As with all deployment names, these are case insensitive.

The deployment-style priorities and flags can be assigned to the individual packages within the Divinefiles. The packages are then intertwined with the regular deployments in a shared workflow.

The Divinefiles do not support the advanced features of the system package managers. For the more complex package installations — e.g., involving particular versions or special package manager options — the regular deployments should be used instead.

Divinefile syntax

The Divinefiles follow the general manifest syntax.

Every Divinefile entry is a list of whitespace-separated package names. The keys flags and priority set the respective attributes for the packages. The priority works for packages in the same way as it does for the deployments.

The following flags are supported for packages.

Table 19. List of supported package flags in Divinefiles

m

By default, if the package is not found to be installed via the system package manager, but is nevertheless available on $PATH, the framework will mark it down as 'Fully installed (by user or OS)' and be done with it. The m flag specifies that the package should be installed exclusively via the system package manager.

[ir] (any of the two lowercase letters)

These flags engage the always-prompt mode for particular primary routines. An urgent prompt will appear on the chosen routines, before the package is processed.

  • i — always prompt during the install routine.

  • r — always prompt during the remove routine.

Within a line, each vertical bar | starts an alt-list, which fully overrides the original list for a particular package manager. Within an alt-list, everything to the left of the first : symbol (colon) is read as the package manager’s name; everything to the right — as the alt-list of packages. The package manager’s name is matched against the $D__OS_PKGMGR variable.

Example of Divinefile
git vim                 # Maintain git and vim with default priority (4096)


(priority:300)          # Set priority to 300 henceforth


(priority:500)  \       # Set priority to 500 for this line only
(r)             \       # Set flag 'prompt before removing' for this line only
node            \       # Maintain node
| apt-get: nodejs npm   # On apt-get, maintain nodejs and npm instead


(os:fedora) \           # Make this line exclusive to Fedora
util-linux-user         # Maintain util-linux-user with priority 300

Multitask deployments

The multitask helpers are a set of partly pre-implemented primary functions for deployments that carry out a series of dissimilar tasks. (For deployments that deal with a series of similar tasks, the queue helpers should be used.)

The multitask helpers provide a way to cram any number of sub-deployments (called tasks) into a multitask deployment. Each task can have its own set of mini-primaries, which are near-identical in behavior to the regular primaries. In particular, the same return codes are supported for the mini-primaries as are for their older siblings. The framework automatically amalgamates the return codes of the tasks into the single return code of the multitask deployment itself.

The multitask helpers can be employed no more than once per deployment.

Setting up a multitask deployment

To assemble a multitask deployment:

  • Set the multitask determinant — an array named D_MLTSK_MAIN, each element of which single-handedly defines a task. The ordinal number of an array element is the task’s number, and the value of that element is the task’s name.

    D_MLTSK_MAIN=( task_one task_two )

    The multitask determinant must be populated before the first multitask helper (d__mltsk_check) is called.

    The determinant array must be continuous (uninterrupted).

  • Implement the (optional) mini-primaries for the tasks, following the naming pattern (for the task named TASK):

    • d_TASK_check — the task-level equivalent of the d_dpl_check function.

    • d_TASK_install — the task-level equivalent of the d_dpl_install function.

    • d_TASK_remove — the task-level equivalent of the d_dpl_remove function.

  • Call the multitask helper primaries as the last commands of the deployment’s primary functions, e.g.:

    d_dpl_check()   { d__mltsk_check;   }
    d_dpl_install() { d__mltsk_install; }
    d_dpl_remove()  { d__mltsk_remove;  }

One difference between the reglar primaries and the mini-primaries:

  • The regular deployments are treated individually: one is checked, and then immediately installed/removed.

  • The tasks are treated collectively: all are sequentially checked, and only then sequentially installed/removed.

Example of multitask deployment
# Delegate the primaries to the multitask helper primaries
d_dpl_check()    { assemble_tasks;  d__mltsk_check;   }
d_dpl_install()  {                  d__mltsk_install; }
d_dpl_remove()   {                  d__mltsk_remove;  }

# This function is the recommended way of organizing logic
assemble_tasks() { D_MLTSK_MAIN=( eat pray love ); }

# Implement the (optional) mini-primaries for the tasks

d_eat_check()     { :; }
d_eat_install()   { :; }
d_eat_remove()    { :; }

d_pray_check()    { :; }
d_pray_install()  { :; }
d_pray_remove()   { :; }

d_love_check()    { :; }
d_love_install()  { :; }
d_love_remove()   { :; }

Multitask hooks

The framework recognizes a set of specially named functions — hooks — that are called at particular junctions of processing a multitask deployment. The hooks have the power to alter the behavior of the multitask deployment via their return codes and add-statuses.

  • Pre- and post- multitask hooks can do arbitrary work.

    • When the multitask check hook returns a non-zero code, the corresponding helper primary shuts down with the code 3 ('Irrelevant or invalid').

      • d_mltsk_pre_check — called before the checking of tasks starts.

      • d_mltsk_post_check — called after the checking of tasks concludes.

    • When the multitask install/remove hook returns a non-zero code, the corresponding helper primary shuts down with the code 2 ('Refused to install/remove').

      • d_mltsk_pre_install — called before (and if) the installation of tasks starts.

      • d_mltsk_post_install — called after the installation of tasks concludes.

      • d_mltsk_pre_remove — called before (and if) the removal of tasks starts.

      • d_mltsk_post_remove — called after the removal of tasks concludes.

  • Pre- and post- task hooks can, too, do arbitrary work.

    • When the task check hook returns a non-zero code, the corresponding mini-primary shuts down with the code 3 ('Irrelevant or invalid').

      • d_TASK_pre_check — called before the checking of that task starts.

      • d_TASK_post_check — called after the checking of that task concludes.

    • When the task install/remove hook returns a non-zero code, the corresponding mini-primary shuts down with the code 2 ('Refused to install/remove').

      • d_TASK_pre_install — called before (and if) the installation of that task starts.

      • d_TASK_post_install — called after the installation of that task concludes.

      • d_TASK_pre_remove — called before (and if) the removal of that task starts.

      • d_TASK_post_remove — called after the removal of that task concludes.

Multitask add-statuses

The multitask deployments can take advantage of the deployment-level add-statuses. On top of that, the framework provides add-statuses that are specific to the multitask deployments.

Table 20. Supported multitask add-statuses

Add-statuses in multitask deployments

D_ADDST_MLTSK_HALT

If set to true, forces halting of the current multitask deployment. No further tasks will be processed.

If set during the check phase, the install/remove phase will still commence for the tasks that have been checked before the halting.

D_ADDST_MLTSK_IRRELEVANT

If set to true during the check phase, stops all further processing of tasks and forces the check code of 3 ('Irrelevant or invalid'). This add-status does not work during the install/remove phases.

Add-statuses of pre- and post- multitask hooks
D_ADDST_MLTSK_CHECK_CODE,
D_ADDST_MLTSK_INSTALL_CODE,
D_ADDST_MLTSK_REMOVE_CODE

These add-statuses allow to override the corresponding code of the helper primary from either the pre- or post- multitask hook.

Add-statuses of pre- and post- task hooks
D_ADDST_TASK_CHECK_CODE,
D_ADDST_TASK_INSTALL_CODE,
D_ADDST_TASK_REMOVE_CODE

These add-statuses allow to override the corresponding code of the current task from either the pre- or post- task hook.

D_ADDST_TASK_FLAGS

Acts as a vehicle for transporting arbitrary single-character flags between the hooks and mini-primaries. Whatever is assigned to this add-status is appended to the D__TASK_FLAGS indicator variable.

Multitask indicators

The multitask deployments can take advantage of the deployment-level indicators. On top of that, the framework provides indicators that are specific to the multitask deployments.

Table 21. List of multitask indicator variables

D__TASK_NUM, D__TASK_NAME

These indicators contain the current task’s ordinal number (D__TASK_NUM, starts at zero) and name (D__TASK_NAME).

D__TASK_IS_FORCED

During the install/remove phase, this indicator reflects whether the current task is being forced. If set to true, this indicator tells that the installation/removal would not have been run if the --force option weren’t provided.

Possible values: true / false.

D__TASK_FLAGS

This indicator contains whatever flags might have been assigned to the current task.

Task code indicators
D__TASK_CHECK_CODE
D__TASK_INSTALL_CODE
D__TASK_REMOVE_CODE

These indicators contain the integer codes returned by the task’s mini-primaries.

Naturally, these indicators are only available after their corresponding mini-primaries have completed their run.

Particularly, the D__TASK_INSTALL_CODE and D__TASK_REMOVE_CODE indicators are only available to the task’s corresponding post- install/remove hooks.

Multitask code indicators
D__MLTSK_CHECK_CODE
D__MLTSK_INSTALL_CODE
D__MLTSK_REMOVE_CODE

These indicators contain the integer codes returned by the task’s helper primaries.

Naturally, these indicators are only available after their corresponding helper primaries have completed their run.

Particularly, the D__MLTSK_INSTALL_CODE and D__MLTSK_REMOVE_CODE indicators are only available to the deployment’s corresponding post- install/remove hooks.

Queues

The queue helpers are a set of partly pre-implemented primary functions for deployments that carry out a series of similar tasks. (For deployments that deal with a series of dissimilar tasks, the multitask helpers should be used.)

The queue helpers provide a way to cram any number of sub-deployments (called queue items) into a deployment. All queue items share the same set of mini-primaries, which are near-identical in behavior to the regular primaries. In particular, the same return codes are supported for the mini-primaries as are for their older siblings. The framework automatically amalgamates the return codes of the queue items into the single return code of the queue itself.

The queue helpers may be employed multiple times in a single deployment under the following conditions:

  • Each invocation of the queue helpers must be contained in its own task in a multitask deployment.

  • The queue must be properly split into sections between the tasks.

Partly pre-implemented specialized queues are also available:

  • Copy-queue — copies files, with backups and comparison.

  • Link-queue — symlinks files, with backups.

  • Github-queue — retrieves Github repositories, with backups and additional checks.

Setting up a queue

To assemble a generic queue within a deployment (or a task):

  • Set the queue determinant — an array named D_QUEUE_MAIN, each element of which single-handedly defines a queue item. The ordinal number of an array element is the item’s number, and the value of that element is the item’s name.

    D_QUEUE_MAIN=( item_one item_two )

    The queue determinant must be populated before the first queue helper (d__queue_check) is called. The queue array may be automatically populated via the queue or asset manifests.

    The determinant array must be continuoue (uninterrupted).

  • Implement the (optional) mini-primaries for the queue items:

    • d_item_check — the queue item equivalent of the d_dpl_check function.

    • d_item_install — the queue item equivalent of the d_dpl_install function.

    • d_item_remove — the queue item equivalent of the d_dpl_remove function.

  • Call the queue helper primaries as the last commands of the deployment’s (or task’s) primary functions, e.g.:

    d_dpl_check()   { d__queue_check;   }
    d_dpl_install() { d__queue_install; }
    d_dpl_remove()  { d__queue_remove;  }

One difference between the reglar primaries and the mini-primaries:

  • The regular deployments are treated individually: one is checked, and then immediately installed/removed.

  • The queue items are treated collectively: all are sequentially checked, and only then sequentially installed/removed.

Example of queue deployment
# Delegate the primaries to the queue helper primaries
d_dpl_check()    { assemble_items;  d__queue_check;   }
d_dpl_install()  {                  d__queue_install; }
d_dpl_remove()   {                  d__queue_remove;  }

# This function is the recommended way of organizing logic
assemble_items() { D_QUEUE_MAIN=( one two three ); }

# Implement the (optional) mini-primaries for the items

d_item_check()     { :; }
d_item_install()   { :; }
d_item_remove()    { :; }

Queue manifests

The queue items, whatever they are, may be separated from the deployment logic into their own file, the queue manifest. The framework detects the asset manifest and automatically assembles the queue determinant from it.

The queue manifest is looked up, by default, at the path of the deployment file, with the .dpl.sh suffix exchanged for .dpl.que. Unlike with the asset manifests, the deployment is free to customize the location of the queue manifest via an add-status.

The queue manifests follow the general manifest syntax, which provides the OS recognition. The manifest flags are not used. On top of that, a queue splitting mechanism is supported:

Table 22. List of queue manifest key-values

(queue: split)

This key-value does not affect the actual queue entries, nor does it support any values other than the pre-defined word split.

The (queue: split) key-value splits the queue. Such splits occur at the exact positions where the key-value is encountered.

Queue hooks

The framework recognizes a set of specially named functions — hooks — that are called at particular junctions of processing a queue. The hooks have the power to alter the behavior of the queue via their return codes and add-statuses.

  • Pre- and post- queue hooks can do arbitrary work.

    • When the queue check hook returns a non-zero code, the corresponding helper primary shuts down with the code 3 ('Irrelevant or invalid').

      • d_queue_pre_check — called before the checking of items starts.

      • d_queue_post_check — called after the checking of items concludes.

    • When the queue install/remove hook returns a non-zero code, the corresponding helper primary shuts down with the code 2 ('Refused to install/remove').

      • d_queue_pre_install — called before (and if) the installation of items starts.

      • d_queue_post_install — called after the installation of items concludes.

      • d_queue_pre_remove — called before (and if) the removal of items starts.

      • d_queue_post_remove — called after the removal of items concludes.

  • Pre- and post- item hooks can, too, do arbitrary work.

    • When the item check hook returns a non-zero code, the corresponding mini-primary shuts down with the code 3 ('Irrelevant or invalid').

      • d_item_pre_check — called before the checking of an item starts.

      • d_item_post_check — called after the checking of an item concludes.

    • When the item install/remove hook returns a non-zero code, the corresponding mini-primary shuts down with the code 2 ('Refused to install/remove').

      • d_item_pre_install — called before (and if) the installation of an item starts.

      • d_item_post_install — called after the installation of an item concludes.

      • d_item_pre_remove — called before (and if) the removal of an item starts.

      • d_item_post_remove — called after the removal of an item concludes.

Queue add-statuses

The queues can take advantage of the deployment-level add-statuses. On top of that, the framework provides add-statuses that are specific to the queues.

Table 23. Supported queue add-statuses

Add-statuses in queues

D_ADDST_QUEUE_MNF_PATH

If set to a non-empty value, directs to look for the queue manifest at that path. To be picked up, this add-status must be set at the root of the deployment script.

D_ADDST_QUEUE_HALT

If set to true, forces halting of the current queue. No further queue items will be processed.

If set during the check phase, the install/remove phase will still commence for the queue items that have been checked before the halting.

D_ADDST_QUEUE_IRRELEVANT

If set to true during the check phase, stops all further processing of that queue section and forces the check code of 3 ('Irrelevant or invalid'). This add-status does not work during the install/remove phases.

Add-statuses of pre- and post- queue hooks
D_ADDST_QUEUE_CHECK_CODE,
D_ADDST_QUEUE_INSTALL_CODE,
D_ADDST_QUEUE_REMOVE_CODE

These add-statuses allow to override the corresponding code of the helper primary from either the pre- or post- queue hook.

Add-statuses of pre- and post- item hooks
D_ADDST_ITEM_CHECK_CODE,
D_ADDST_ITEM_INSTALL_CODE,
D_ADDST_ITEM_REMOVE_CODE

These add-statuses allow to override the corresponding code of the current queue item from either the pre- or post- item hook.

D_ADDST_ITEM_FLAGS

Acts as a vehicle for transporting arbitrary single-character flags between the hooks and mini-primaries. Whatever is assigned to this add-status is appended to the D__ITEM_FLAGS indicator variable.

Queue indicators

The queues can take advantage of the deployment-level indicators. On top of that, the framework provides indicators that are specific to the queues.

Table 24. List of queue indicator variables

D__ITEM_NUM, D__ITEM_NAME

These indicators contain the current queue item’s ordinal number (D__ITEM_NUM, starts at zero) and name (D__ITEM_NAME).

D__ITEM_IS_FORCED

During the install/remove phase, this indicator reflects whether the current queue item is being forced. If set to true, this indicator tells that the installation/removal would not have been run if the --force option weren’t provided.

Possible values: true / false.

D__ITEM_FLAGS

This indicator contains whatever flags might have been assigned to the current queue item. Additionally, in the auto-queues, this indicator is also pre-populated with the flags gleaned from the asset or queue manifest.

Item code indicators
D__ITEM_CHECK_CODE
D__ITEM_INSTALL_CODE
D__ITEM_REMOVE_CODE

These indicators contain the integer codes returned by the queue item’s mini-primaries.

Naturally, these indicators are only available after their corresponding mini-primaries have completed their run.

Particularly, the D__ITEM_INSTALL_CODE and D__ITEM_REMOVE_CODE indicators are only available to the queue item’s corresponding post- install/remove hooks.

Queue code indicators
D__QUEUE_CHECK_CODE
D__QUEUE_INSTALL_CODE
D__QUEUE_REMOVE_CODE

These indicators contain the integer codes returned by the queue item’s helper primaries.

Naturally, these indicators are only available after their corresponding helper primaries have completed their run.

Particularly, the D__QUEUE_INSTALL_CODE and D__QUEUE_REMOVE_CODE indicators are only available to the deployment’s corresponding post- install/remove hooks.

Multiple queues

The queue helpers can be employed more than once per deployment. When that happens, all types of queues (generic and the various specialized types) share the same internal mechanism and the same determinant, D_QUEUE_MAIN. Consequently, when a deployment contains multiple queues, the queue must be split into queue sections.

Queue sections are delimited by the ordinal number of their first elements in the queue array (determinant). Such delimiters are called the queue splits. A section spans from its split until the split of the next section (the latter not inclusive), or until the end of the queue array. The first section always implicitly starts at the first element of the queue array. So, the total number of queue sections is the number of splits plus one. (The word 'first' is used for readability; the underlying Bash numberings start at 0.)

There are two ways to split a queue:

  • In the deployment code, call the d__queue_split function:

    D_QUEUE_MAIN=( elem0-0 elem0-1 elem0-2 )  # Initialize with first section
    
    d__queue_split  ## Without arguments, splits at the current right edge of the
                    #. queue array (in this example - position 3)
    
    D_QUEUE_MAIN+=( elem1-0 elem1-1 elem2-0 elem2-1 )  # Append two more sections
    
    d__queue_split 5  ## Manual split after the fact, at position 5, 'elem2-0'
  • In the asset and queue manifests, the automatically populated queue array can be split by including the key-value (queue: split) at the desired split position.

The queue determinant may be put together incrementally, but a section of it must be fully assembled by the time its first helper primary is called.

Whenever a queue section uses multiple arrays to store relevant data (which is true, for instance, for all specialized queues), a care must be taken to ensure that all arrays use the same indices for the same queue items.

It is crucial to understand, that the framework unsets (deletes) the queue’s mini-primaries, add-statuses, and hooks as soon as the current queue section has used them fully. Each subsequent section needs to define and implement its own.

Auto-queues

Regardless of whether the deployment employs the queues, the framework automatically populates the queue determinant (and some accompanying arrays) whenever a suitable manifest is encountered.

Such auto-queues are built at two points in processing of a deployment. The order is significant here, because the queues supersede each other.

  1. Before the deployment script is sourced, an auto-queue is built from the deployment’s asset manifest:

    This kind of auto-queue can be handily complemented with the auto-targeting.

  2. Immediately after the deployment script is sourced (and before any deployment functions are called), an auto-queue is built from the deployment’s queue manifest:

An auto-queue is only built if the corresponding manifest exists. Both types of auto-queues support splitting the queue (via the manifest syntax).

Queue auto-targeting

When the queue section operates in the single target directory, the D_QUEUE_TARGETS array — which is used by all specialized queues — may be automatically populated. Such queue auto-targeting requires:

  • The single target directory for all queue items must be known. It may not exist yet.

  • The queue determinant D_QUEUE_MAIN must be filled with relative file paths.

(The auto-targeting works well with an auto-queue built from the asset manifest.)

The auto-targeting simply populates the D_QUEUE_TARGETS array at the given queue section, with relative paths from the D_QUEUE_MAIN array, prepended by the target directory path.

The auto-targeting of a queue section must be initiated explicitly by calling the d__queue_target function:

D_QUEUE_MAIN=( 'rel/path0' 'rel/path1' 'rel/path2' )  # Initialize the queue

d__queue_target '/target/dir'  # Without options, whole queue is auto-targeted

## This effectively populates the target array as such:
#>  D_QUEUE_TARGETS=( \
#>    /target/dir/rel/path0 \
#>    /target/dir/rel/path1 \
#>    /target/dir/rel/path2 \
#>  )
#

The d__queue_target function supports the following arguments:

d__queue_target [-s|--section SECTNUM] [--] TARGET_DIR

The TARGET_DIR must be non-empty; trailing slashes in it are ignored. If the --section option is given, its SECTNUM argument must the zero-based number of the queue section that is to be auto-targeted. Without the --section option, the whole queue is affected.

Specialized queues

The specialized queues are partly pre-implemented generic queues:

  • Copy-queue — copies files, with backups and comparison.

  • Link-queue — symlinks files, with backups.

  • Github-queue — retrieves Github repositories, with backups and additional checks.

All specialized queues use the same underlying mechanism. A care must be taken when employing more than one queue of any type in a deployment.

Copy-queues

The copy-queue takes a list of asset paths, a list of destination paths, and sequentially copies the former onto the latter. No overwriting is done, although this can be influenced. The stashing system is used to track the installations of the copy-queue.

Setting up a copy-queue

To assemble a copy-queue within a deployment (or a task):

  • Set the queue determinant, D_QUEUE_MAIN. Any names may be used for the copy-queue items. An obvious (and the recommended) choice — is the relative paths to the assets.

  • For each index in the determinant array, populate the following special arrays:

    • D_QUEUE_ASSETS — the absolute paths to the assets.

    • D_QUEUE_TARGETS — the absolute paths to the destinations.

  • Call the copy-queue helper primaries as the last commands of the deployment’s (or task’s) primary functions, e.g.:

    d_dpl_check()   { d__copy_queue_check;   }
    d_dpl_install() { d__copy_queue_install; }
    d_dpl_remove()  { d__copy_queue_remove;  }

The assembly of the copy-queue arrays can be automated, especially when copying assets.

Assembled copy-queue example
# ~/.grail/dpls/copy-queue-example.dpl.sh

d_dpl_check()   { assemble_queue; d__copy_queue_check;   }
d_dpl_install() {                 d__copy_queue_install; }
d_dpl_remove()  {                 d__copy_queue_remove;  }

assemble_queue()
{
  D_QUEUE_MAIN=( '.hushlogin' )
  D_QUEUE_ASSETS=( "$D__DPL_ASSET_DIR/.hushlogin" )
  D_QUEUE_TARGETS=( "$HOME/.hushlogin" )
}

Copy-queue hooks, add-statuses, and indicators

The copy-queue supports the equivalent set of hooks as the generic queue, with the d_ prefix exchanged for the d_copy_ prefix. (E.g., d_item_post_install becomes d_copy_item_post_install.) Other than the name difference, the hooks behave identically.

The copy-queue also supports the same add-statuses and indicators, with additions to the former:

Table 25. Supported copy-queue add-statuses

D_ADDST_COPY_QUEUE_EXACT
D_ADDST_COPY_ITEM_EXACT

By default, the copy-queue merely ensures that an asset by the same name exists at the destination, and, if not, does the copying.

If set to true, these add-statuses ensure that the exact copy of the provided asset exists at the destination, not just a namesake. The two add-statuses differ in scope:

  • D_ADDST_COPY_QUEUE_EXACT — affects the entire copy-queue; must be set before the copy-queue’s first helper primary (d__copy_queue_check) is called.

  • D_ADDST_COPY_ITEM_EXACT — affects the current copy-queue item; must be set in that item’s pre-check hook (d_copy_queue_pre_check).

The same effect can be achieved by setting the e flag for the relevant assets in either the asset or queue manifests.

Link-queues

The link-queue takes a list of asset paths, a list of destination paths, and sequentially creates symlinks at the latter, pointing to the former. Any pre-existing files at the symlink locations are backed up. The stashing system is used to track the installations of the link-queue.

Setting up a link-queue

To assemble a link-queue within a deployment (or a task):

  • Set the queue determinant, D_QUEUE_MAIN. Any names may be used for the link-queue items. An obvious (and the recommended) choice — is the relative paths to the assets.

  • For each index in the determinant array, populate the following special arrays:

    • D_QUEUE_ASSETS — the absolute paths to the assets.

    • D_QUEUE_TARGETS — the absolute paths to the symlink locations.

  • Call the link-queue helper primaries as the last commands of the deployment’s (or task’s) primary functions, e.g.:

    d_dpl_check()   { d__link_queue_check;   }
    d_dpl_install() { d__link_queue_install; }
    d_dpl_remove()  { d__link_queue_remove;  }

The assembly of the link-queue arrays can be automated, especially when symlinking assets.

Assembled link-queue example
# ~/.grail/dpls/link-queue-example.dpl.sh

d_dpl_check()   { assemble_queue; d__link_queue_check;   }
d_dpl_install() {                 d__link_queue_install; }
d_dpl_remove()  {                 d__link_queue_remove;  }

assemble_queue()
{
  D_QUEUE_MAIN=( '.bashrc' )
  D_QUEUE_ASSETS=( "$D__DPL_ASSET_DIR/.bashrc" )
  D_QUEUE_TARGETS=( "$HOME/.bashrc" )
}

Link-queue hooks, add-statuses, and indicators

The link-queue supports the equivalent set of hooks as the generic queue, with the d_ prefix exchanged for the d_link_ prefix. (E.g., d_item_pre_remove becomes d_link_item_pre_remove.) Other than the name difference, the hooks behave identically.

Github-queues

The Github-queue takes a list of Github repository handles (username/repository), a list of destination paths, and sequentially clones/downloads the former into the latter. The stashing system is used to track the installations of the Github-queue.

Setting up a Github-queue

To assemble a Github-queue within a deployment (or a task):

  • Set the queue determinant, D_QUEUE_MAIN. The names of the Github-queue items must be the Github repository handles (username/repository).

  • For each index in the determinant array, populate the following special arrays:

    • D_QUEUE_TARGETS — the absolute paths to the repository destinations.

  • Call the Github-queue helper primaries as the last commands of the deployment’s (or task’s) primary functions, e.g.:

    d_dpl_check()   { d__gh_queue_check;   }
    d_dpl_install() { d__gh_queue_install; }
    d_dpl_remove()  { d__gh_queue_remove;  }

The assembly of the Github-queue arrays can be automated.

Assembled Github-queue example
# ~/.grail/dpls/github-queue-example.dpl.sh

d_dpl_check()   { assemble_queue; d__gh_queue_check;   }
d_dpl_install() {                 d__gh_queue_install; }
d_dpl_remove()  {                 d__gh_queue_remove;  }

assemble_queue()
{
  D_QUEUE_MAIN=( 'ohmyzsh/ohmyzsh' )
  D_QUEUE_TARGETS=( "$HOME/.oh-my-zsh" )
}

Github-queue hooks, add-statuses, and indicators

The Github-queue supports the equivalent set of hooks as the generic queue, with the d_ prefix exchanged for the d_gh_ prefix. (E.g., d_item_post_check becomes d_gh_item_post_check.) Other than the name difference, the hooks behave identically.

Advanced features

Divine.dotfiles offers mechanisms that facilitate creation of better, stronger, faster deployments.

Naming convention

Divine.dotfiles uses a system of prefixes to gradually separate the framework’s inner workings.

  • The single-underscore prefixes are for entities to be implemented by a deployment:

  • The double-underscore prefixes are for entities to be accessed by a deployment:

    • D__NAME — the variables to be read, e.g., the indicators.

    • d__name — the functions to be called, e.g., the d__stash function.

  • The triple-underscore prefixes are for entities that are strictly internal:

    • d___name — the functions that the framework uses for logic encapsulation.

To stay on the safe side, the deployment code needs to stay away from identifiers that start with the letter d/D followed by an underscore, unless a framework mechanism is being employed.

Divine workflow

The Divine workflow is the recommended way to organize Bash code within deployments. The framework provides a set of functions that facilitate breaking down Bash code into meaningful blocks. The goal of the workflow system is to both annotate the code and automatically provide useful debug output.

Central to the idea of the Divine workflow is the concept of workflow context. The workflow context is a stack (similar to the function call stack) that at any moment contains the hierarchy of tasks currently being performed. The context stack can be visualized like this:

Illustration of the Divine workflow context stack
0. Running `install` routine
1. Installing deployments at priority `4096`
2. Deployment `example`
---
3. Executing a particular task within the deployment
4. Executing a sub-task

The context stack starts at the root and grows down to arbitrary depth. Context items are pushed and popped from the bottom. Notches — represented by the three hyphens (---) in the illustration — delimit the logical stratas.

With the idea of context in mind, the Divine workflow boils down to the following principles:

  • The logic should be encapsulated in functions.

  • The workflow context tree should be grown by calling the d__context function:

    • d__context notch — to place a notch.

    • d__context push — to push a workflow item onto the stack.

  • Whenever a context branch needs to fail:

  • To interact with the user along the way:

    • Non-failure alerts should be delivered via the d__notify function.

    • Prompts could be initiated via the d__prompt function.

  • To finalize successful runs:

    • d__context pop — to pop a workflow item from the stack.

    • d__context lop — to slash the context stack up to and including the latest notch.

If a push is prepended to every logical unit of code, and then a matching pop is appended at the end, a useful pattern of breadcrumbs arises in the debug output. The Divine workflow functions (d__notify, d__prompt, d__fail, d__cmd, d__pipe, d__require) may include the state of the context stack in their output.

Divine workflow in action
d__dpl_install()
{
  d__context notch
  d__context push 'Installing feature'
  d__context push 'Performing critical work'
  d__cmd perform_critical_work --ARG-- "$some_arg" \
    --else-- 'Nothing was installed' || return 1
  d__context lop
  return 0
}

With the default verbosity level, the code above produces no output when successful. If something goes wrong in the critical task, a message similar to the following will be printed:

==> Command failed: perform_critical_work ARG
        ARG: 'whetever $some_arg variable contained'
    Context: Installing feature
             Performing critical work
    Result: Nothing was installed

Function d__context

The function d__context manipulates the context stack of the Divine workflow. The main section ties together the usage of the Divine workflow.

d__context [-!dlnqsvx] [-t TITLE] [--] push|pop|notch|lop DESCRIPTION|[DESCRIPTION]

The usage per routine is:

  • push DESCRIPTION — appends the DESCRIPTION item to the bottom of the context stack.

  • pop [DESCRIPTION] — removes an item from the bottom of the context stack.

    If given, the optional DESCRIPTION substitutes for the actual description of the popped item.

  • notch — places a notch past the bottom of the context stack.

  • lop — repeatedly executes a pop until the latest notch (or root) is reached, and then removes that notch.

All stack manipulations trigger a debug message that honors the global verbosity level.

Any number of notches can be made, as long as they are not duplicated at the same position. Within the context stack, the latest pushed item is called the tip, and all items pushed after the latest notch are collectively called the head.

Each item on the context stack must be given a DESCRIPTION. Stylistically, the DESCRIPTION should be a single sentence (no full stop at the end), worded around either a noun or a gerund (yeah, that’s important!). Repetition of information from the above levels should be minimized.

Output layout of d__context
# Pushing an item
==> Start: Description of the context item

# Popping an item
==> End: Description of the context item

# Making a notch
==> Notched: At position X

# Removing a notch
==> De-notched: At position X

The return codes are:

  • 0 — Stack modified as requested.

  • 1 — Stack not modified: no arguments or unrecognized first argument.

  • 2 — Stack not modified: pushing without a DESCRIPTION.

  • 3 — Stack not modified: popping from an empty stack.

  • 4 — Stack not modified: notching at the same position.

Table 26. List of d__context options

-t TITLE, --title TITLE

Sets a custom title for the debug message. The title is not a part of the DESCRIPTION. All operations have default titles (these are shown in the illustration above in bold).

-n, --newline

When this option is given: if the function produces any output, an extra newline is prepended to it.

Quiet options
-q, --quiet (repeatable)
-l, --loud

The quiet options designate the quiet level for the current call. The function’s output is printed only if the global verbosity level is greater than or equal to the quiet level.

When the quiet options are read, left-to-right, the quiet level starts at zero:

  • Each --quiet option increments the quiet level by one.

  • The --loud option sets the quiet level to zero.

However, if none of the quiet options are given, the default quiet levels per operation are:

  • push2

  • pop3

  • notch4

  • lop4

    The default quiet level for the lop applies to the underlying pops. For the pops to be performed at a different quiet level, they must be made separately.

Styling options
-d, --debug (default)
-!, --alert
-v, --success
-x, --failure
-s, --skip

The styling options are only relevant if the terminal coloring is available. Otherwise, they do nothing.

The titles are formatted in bold, and on top of that:

  • The --debug option paints the entire output in cyan.

  • The --alert option paints the introductory arrow in yellow.

  • The --success option paints the introductory arrow in green.

  • The --failure option paints the introductory arrow in red.

  • The --skip option paints the introductory arrow in white.

These options are not combined and the last option given wins.

Function d__notify

The d__notify function is a debug printer that does not cause any side effects. The main section ties together the usage of the Divine workflow.

d__notify [-!1cdhlnqsuvx] [-t TITLE] [--] [DESCRIPTION...]

Whether the output is printed depends on the global verbosity level.

Output layout of d__notify (without any arguments)
==> TITLE
Output layout of d__notify (all optional parts included)
==> TITLE: DESCRIPTION-0
    DESCRIPTION-1
    ...
    Context: CONTEXT-0
             CONTEXT-1
             ...
  • TITLE(optional) a short heading.

  • DESCRIPTION(optional) an elaboration.

  • CONTEXT(optional) some part of the context stack (depending on options).

    The entire context block is omitted if the context stack is not requested or is empty.

The return codes are:

  • 0 — Always.

Table 27. List of d__notify options

-u, --sudo

Directs to print the notification only if the caller lacks the sudo privilege. Automatically makes the notification --loud.

With this option, new defaults are in effect:

  • TITLEPassword prompt.

  • DESCRIPTIONThe upcoming command requires sudo privileges.

-t TITLE, --title TITLE

Sets a custom title for the notification.

Without this option, the TITLE defaults to:

  • With a DESCRIPTION — omitted.

  • Without a DESCRIPTIONGeneric alert.

-n, --newline

When this option is given: if the function produces any output, an extra newline is prepended to it.

Quiet options
-q, --quiet (repeatable)
-l, --loud

The quiet options designate the quiet level for the current call. The function’s output is printed only if the global verbosity level is greater than or equal to the quiet level.

When the quiet options are read, left-to-right, the quiet level starts at zero:

  • Each --quiet option increments the quiet level by one.

  • The --loud option sets the quiet level to zero.

However, if none of the quiet options are given, the default quiet level is 1.

Context options
-c, --context-all
-h, --context-head
-1, --context-tip

Includes the context block in the output with the following content:

  • --context-all — the entire context stack;

  • --context-head — the head of the context stack (the items pushed since the last notch);

  • --context-tip — the tip of the context stack (the last pushed item).

These options are not combined and the last option given wins.

Styling options
-d, --debug (default)
-!, --alert
-v, --success
-x, --failure
-s, --skip

The styling options are only relevant if the terminal coloring is available. Otherwise, they do nothing.

The titles are formatted in bold, and on top of that:

  • The --debug option paints the entire output in cyan.

  • The --alert option paints the introductory arrow in yellow.

  • The --success option paints the introductory arrow in green.

  • The --failure option paints the introductory arrow in red.

  • The --skip option paints the introductory arrow in white.

These options are not combined and the last option given wins.

Special para-options
-t-
-n-
-i-

The para-options work only to the right of the option-argument separator (--).

The para-options are shorthand for linebreaks, indentation, and bold titles.

  • -t- — treats the next WORD in the DESCRIPTION as a title.

    The title is styled in bold (if the terminal coloring is available) and is followed by a colon.

  • -n- — inserts a newline followed by the arrow-width indentation (four spaces).

  • -i- — similar to the -n- para-option, but the indentation is doubled.

Function d__prompt

The function d__prompt is the preferred mechanism for requesting a confirmation from the user. The main section ties together the usage of the Divine workflow.

d__prompt [-!1bchknqsvxy] [-p PROMPT] [-a ANSWER] [-t TITLE] [--] [DESCRIPTION...]

Two prompting modes are supported:

  • The decision prompt (yes or no): returns 0 for the affirmative answer and 1 for the negatory answer.

  • The any-key prompt: returns 0 for any key press.

Both modes support the --or-quit option, which adds an additional 'quit' response with the return code of 2.

Output layout of d__prompt (without any arguments)
PROMPT KEYS
Output layout of d__prompt (all optional parts included)
==> TITLE: DESCRIPTION-0
    DESCRIPTION-1
    ...
    Context: CONTEXT-0
             CONTEXT-1
             ...
    PROMPT KEYS

If the prompt contains no optional parts, it is considered a one-liner, and the introductory arrow is omitted.

  • PROMPT — a short question. Defaults to Proceed?.

  • KEYS — the reference of accepted keys:

    • The decision prompt: [y/n]

    • The decision prompt (with the --or-quit option): [y/n/q]

    • The any-key prompt: [<any key>]

    • The any-key prompt (with the --or-quit option): [<any key>/q]

  • TITLE(optional) a short heading.

    If the DECSRIPTION is empty and if the prompt is not a one-liner, defaults to User attention required.

  • DESCRIPTION(optional) an elaboration.

  • CONTEXT(optional) some part of the context stack (depending on options).

    The entire context block is omitted if the context stack is not requested or is empty.

Table 28. List of d__prompt options

Prompting modes
-y, --yes-no (default)
-k, --any-key

The prompting mode:

  • --yes-no — the decision prompt (yes or no).

  • --any-key — the any-key prompt.

These options are not combined and the last option given wins.

-q, --or-quit

In both prompting modes, provides an extra option: 'quit'. The returned value for the 'quit' option is always 2.

-t TITLE, --title TITLE

Sets a custom title for the leading line.

Without this option, the TITLE defaults to:

  • With a DESCRIPTION — omitted.

  • Without a DESCRIPTIONUser attention required.

-p PROMPT, --prompt PROMPT

Sets a custom PROMPT. Without this option, the PROMPT defaults to Proceed?.

-a ANSWER, --answer ANSWER

In the decision mode:

  • If the value of ANSWER is true, returns 0 without prompting.

  • If the value of ANSWER is false, returns 1 without prompting.

In the any-key mode:

  • If the value of ANSWER is either true or false, returns 0 without prompting.

Passing the value of the $D__OPT_ANSWER variable into the --answer option makes the prompt obey the <<answer that the user provides to the intervention utility.

-n, --newline

When this option is given: if the function produces any output, an extra newline is prepended to it.

Context options
-c, --context-all
-h, --context-head
-1, --context-tip

Includes the context block in the output with the following content:

  • --context-all — the entire context stack;

  • --context-head — the head of the context stack (the items pushed since the last notch);

  • --context-tip — the tip of the context stack (the last pushed item).

These options are not combined and the last option given wins.

Styling options
-d, --debug (default)
-!, --alert
-v, --success
-x, --failure
-s, --skip
-b, --bare

The styling options are only relevant if the terminal coloring is available. Otherwise, they do nothing.

The titles are formatted in bold, and on top of that:

  • The --debug option paints the entire output in cyan.

  • The --alert option paints the introductory arrow in yellow.

  • The --success option paints the introductory arrow in green.

  • The --failure option paints the introductory arrow in red.

  • The --skip option paints the introductory arrow in white.

  • The --bare option removes the coloring completely.

These options are not combined and the last option given wins.

Special para-options
-t-
-n-
-i-

The para-options work only to the right of the option-argument separator (--).

The para-options are shorthand for linebreaks, indentation, and bold titles.

  • -t- — treats the next WORD in the DESCRIPTION as a title.

    The title is styled in bold (if the terminal coloring is available) and is followed by a colon.

  • -n- — inserts a newline followed by the arrow-width indentation (four spaces).

  • -i- — similar to the -n- para-option, but the indentation is doubled.

Function d__fail

The function d__fail announces a failure to the user and lops the head of the workflow context stack. The main section ties together the usage of the Divine workflow.

d__fail [-n] [-t TITLE] [--] [DESCRIPTION...]

Every call to this function internally calls d__context lop. This function should not be used for non-consequential failures that do not affect the workflow context. (For such things, d__notify --failure works nicely.)

The return codes are:

  • 0 — Always, as in 'failed successfully'.

Output layout of d__fail (without any arguments and with empty context stack)
==> TITLE
Output layout of d__fail (all optional parts included)
==> TITLE: DESCRIPTION-0
    DESCRIPTION-1
    ...
    Context: CONTEXT-0
             CONTEXT-1
             ...
  • TITLE(optional) a short heading.

  • DESCRIPTION(optional) Short elaboration on the failure.

  • CONTEXT — The head of the context stack, which is about to be lopped off. The entire context block is omitted if the context stack is empty.

Table 29. List of d__fail options

-t TITLE, --title TITLE

Sets a custom title for the failure message.

Without this option, the TITLE defaults to:

  • With a DESCRIPTIONFailure.

  • Without a DESCRIPTIONSomething went wrong.

-n, --newline

When this option is given: if the function produces any output, an extra newline is prepended to it.

Special para-options
-t-
-n-
-i-

The para-options work only to the right of the option-argument separator (--).

The para-options are shorthand for linebreaks, indentation, and bold titles.

  • -t- — treats the next WORD in the DESCRIPTION as a title.

    The title is styled in bold (if the terminal coloring is available) and is followed by a colon.

  • -n- — inserts a newline followed by the arrow-width indentation (four spaces).

  • -i- — similar to the -n- para-option, but the indentation is doubled.

Function d__cmd

The function d__cmd is a wrapper around a single simple Bash command. The main section ties together the usage of the Divine workflow.

d__cmd [options] [----] CMD...

The wrapper executes the command CMD as normal, then inspects its return code. If the return code is non-zero, it prints a failure message (similar to the output of the d__fail function in appearance), and then calls d__context lop. The output is titled, by default, Command failed, and shows the command that has failed, along with the current context stack.

The command CMD must be a single simple command, consisting of any number of WORDs. Naturally, Bash will parse the call to the wrapper function before the underlying command. As such, here are some examples of what the CMD can and cannot contain:

  • Yes: The simple commands, e.g., test, [, or [[.

  • Yes: The variable expansions.

  • Yes: The input redirections.

  • Maybe: The output redirections.

    (These will affect the wrapper function, which might just do exactly what is needed.)

  • NO: The operators && and ||.

    The d__require function provides the support for these.

  • NO: The pipe operator |.

    The d__pipe function provides the support for this.

  • NO: The negation operator !.

    The --neg-- option provides support for this.

  • NO: The advanced constructs such as if, or case, or the arithmetic context.

The return codes are:

  • 0 — The CMD returned zero (after the optional negation).

  • 1 — The CMD returned non-zero (after the optional negation).

  • 2 — Called without arguments.

Output layout of d__cmd in case of failure (all optional parts included)
==> TITLE: CMD
        LABEL-0: 'WORD-0'
        LABEL-1: 'WORD-1'
        ...
    Context: CONTEXT-0
             CONTEXT-1
             ...
    Circumstances: CRCM
    Result: RSLT
  • TITLE — A short heading.

  • CMD — The command that failed, with labels pasted in.

  • LABEL — (optional) The list of labels and their disambiguations. The labels are created via corresponding options, described below.

  • CONTEXT — The head of the context stack, which is about to be lopped off. The entire context block is omitted if the context stack is empty.

  • CRCM — (optional) The description of circumstances of the command call, if provided by the caller.

  • RSLT — (optional) The description of consequences of the command failure, if provided by the caller.

Table 30. List of d__cmd options

----

Stops processing options for the wrapper function, and reads the rest of the arguments as parts of the CMD.

--neg--

Negates the return code of the CMD, as if it is prepended with the ! operator.

Output suppression modes
--sn-- (default)
--so--
--se--
--sb--

  • --sn-- — suppresses neither the stdout nor the stderr of the CMD, as is default.

  • --so-- — suppresses the stdout of the CMD.

  • --se-- — suppresses the stderr of the CMD.

  • --sb-- — suppresses both the stdout and the stderr of the CMD.

These options are not combined and the last option given wins.

Quiet options
--q--, --q…-- (repeatable)
--l--

The quiet options designate the quiet level for the current call. The CMD's output (if any occurs and is not suppressed) is printed only if the global verbosity level is greater than or equal to the quiet level.

By default, the quiet level does not affect the appearance of the failure output.

When the quiet options are read, left-to-right, the quiet level starts at zero:

  • Each --q-- option increments the quiet level by one.

    This particular option can be repeated within the hyphens, e.g., --qqq--. For efficiency, only the first character is checked to be q, the others are simply counted.

  • The --l-- option sets the quiet level to zero.

However, if none of the quiet options are given, the default quiet level is 0.

Semantics of failure

--opt--

Makes the CMD optional: if there is a failure, the head of the context stacked is not lopped off, and the failure output is styled less urgently.

When a command is marked optional, its failure output is printed depending on the quiet level.

--alrt-- TITLE

Sets a custom title for the failure output.

--crcm-- CRCM

Sets a custom circumstances description for the failure output.

--else-- RSLT

Sets a custom consequences description for the failure output.

Label option
--LABEL--

The --LABEL-- option must precede a single WORD of the CMD. The LABEL part should be an alphanumerical word.

Normally the failure output prints the offending CMD in its entirety, which can get quite lengthy. Labeled WORDs appear in the CMD as their labels, and the labels are spelled out below.

For example, the command d__cmd [ --ZERO-- 0 -eq --ONE-- 1 ] will produce the following failure output:

==> Command failed: [ ZERO -eq ONE ]
        ZERO: '0'
        ONE: '1'

Label backreference options
--#NUM--

A previously assigned label may be re-used if the WORD it marks is used multiple times within the CMD. The labels are automatically numbered, starting from zero. The backreference consist of a hash/pound symbol (#), followed by the number of the previous label. The framework does not check whether the WORDs marked by the same label are actually identical.

Function d__pipe

The d__pipe function extends the d__cmd function by adding the possibility to chain up to three commands in a continuous Bash pipe.

d__pipe [options] [----] CMD...

The reference for the d__cmd function applies fully wherever not overridden by this section. The actual pipe operator (|) cannot be used directly, because it would be parsed with the wrapper function. Instead, the special pipe option is added. When the d__pipe function is used without the special options, it becomes functionally identical to the d__cmd function.

If the --so-- or the --sb-- option is used, the stdout is only suppressed for the last command in the pipe.

Table 31. List of d__pipe options (on top of those in d__cmd)

Pipe option
--p--, --P--, --pipe--

Inserts the Bash’s pipe operator (|) at that location. Only the first two instances of this option are recognized (a hard limit).

--retNUM--

Commands in the pipe are numbered starting from zero, left to right. If this option is given, the NUM represents the number of the command that represents the success of the whole pipe.

Without this option, the last command in the pipe is used.

Function d__require

The d__pipe function extends the d__cmd function by adding the possibility to chain up to three commands with the && and || operators.

d__require [options] [----] CMD...

The reference for the d__cmd function applies fully wherever not overridden by this section. The actual && and || operators cannot be used directly, because they would be parsed with the wrapper function. Instead, the special options are added. When the d__require function is used without the special options, it becomes functionally similar to the d__cmd function.

The d__require function changes semantics a little: while the d__cmd function is intended to cause side effects, this version is intended for checks against certain requirements.

The option --neg-- applies to the individual commands within the and/or chain. It has to be used for every negated requirement, at any time before the start of the next requirement.

Table 32. List of d__require options (on top of those in d__cmd)

And/or options (only the first two are recognized)

And/or options
--and--, --AND--
--or--, --OR--

  • --and-- — inserts the && operator at that location.

  • --or-- — inserts the || operator at that location.

Stash

Divine.dotfiles provides a file-based key-value storage/retrieval system called the stash.

There are three levels of stashing system:

  • The deployment stash — exclusive to the current deployment on the current machine. This is the default.

    Stored in state/stash/DPL-NAME/.stash.cfg.

  • The root stash — shared by all deployments on the current machine.

    Stored in state/stash/.stash.cfg.

  • The Grail stash — shared by all deployments across all machines that use the same Grail.

    Stored in ~/.grail/.stash.cfg.

For the curious:

  • The records of attaching third-party bundles are stored in the Grail stash.

  • The records of installing the optional framework dependencies are stored in the root stash.

The rules of the key-value store are:

  • The keys must consist of:

    • alphanumeric characters;

    • underscores (_) and hyphens (-).

  • The values must not exceed single line of text, but are otherwise unrestricted, and may be empty.

  • Multiple instances of a key are allowed, the values may be duplicate.

The stashing system is accessible via the d__stash function.

d__stash [-drgq] [--] [ CMD [ KEY [VALUE] ] ]

Depending on first argument, usage is as follows.

Table 33. Usage patterns of d__stash

d__stash has KEY [VALUE]

  • If the VALUE is not given: checks whether the stash contains at least one KEY with any value.

  • If the VALUE is given: checks whether the stash contains at least one KEY that is set to that VALUE.

Returns 0 if so, or 1 otherwise.

d__stash set KEY [VALUE]

Ensures that the stash contains a single instance of the KEY, and that it is set to the VALUE.

Returns 0 on success, or 1 otherwise.

d__stash add KEY [VALUE]

Adds one instance of the KEY and sets it to the VALUE, regardless of the possibly pre-existing instances of the KEY.

Returns 0 on success, or 1 otherwise.

d__stash get KEY

Prints the value of the first instance of the KEY to the stdout.

Returns 0 on success (even if nothing was printed), or 1 otherwise.

d__stash list KEY

Prints each value of the KEY on a new line to the stdout.

Returns 0 on success (even if nothing was printed), or 1 otherwise.

d__stash unset KEY [VALUE]

  • If the VALUE is not given: removes all instances of the KEY.

  • If the VALUE is given: removes each instance of the KEY that is set to the VALUE.

Returns 0 on success (even if nothing was removed), or 1 otherwise.

d__stash list-keys

Prints each KEY currently in the stash on a new line to the stdout. Possible duplicate keys are not pruned.

Returns 0 on success (even if nothing was printed), or 1 otherwise.

d__stash clear

Clears all records from this stash.

If called with an unsupported routine name, the function prints an error and returns 3.

Table 34. List of d__stash options

-q, --quiet

By default, any inconsistency during stashing triggers a loud error message. The --quiet option directs to instead honor the global verbosity level.

This option is intended for cases when not using the stash is also a viable option.

Stash level options
--d, --dpl (default)
--r, --root
--g, --grail

These options are not combined and the last option given wins.

Function d__md5

The framework exstensively uses the MD5 checksums to indirectly compare files and strings. The d__md5 function provides a cross-platform way of calculating an the MD5 checksums. Under the hood it uses the first utility found: md5sum, md5, or openssl.

d__md5 [-s STRING] | [PATH]
  • One checksum is calculated per call.

  • Either a string or a path to a file may be given.

  • It is up to the caller to ensure that the path exists and is readable.

  • The checksum is printed to the stdout.

The d__md5 function returns zero on success and non-zero if something goes wrong.

Backups

The framework adheres to the zero data loss and the reversibility policies. To assist in that, the framework provides two functions that facilitate backing up and restoring files.

Function d__push_backup

The function d__push_backup vacates the given path by pushing whatever exists at it to a backup location.

d__push_backup [--] ORIG_PATH [BACKUP_PATH]

The emptying of the ORIG_PATH is a priority; if it sits empty, a success code is immediately returned.

For the backup path, the first available strategy is employed:

  • If a BACKUP_PATH is given, it is used. If it turns out unusable, an error code is returned, without attempting other strategies.

  • If called from a deployment, the backup path is generated in the $D__DPL_BACKUP_DIR directory, and named by concatenating:

    • the MD5 checksum of the ORIG_PATH;

    • the .bak suffix.

  • If none of the above works, an error code is returned.

If the generated backup path happens to be occupied, it is interpreted as a previously made backup. Previous backups are never overwritten. Instead, this function repeatedly appends an incrementing numerical suffix (-1, -2, etc.) to the backup’s name until it finds a path that is not yet occupied. These numerical suffixes are hard capped at 1000.

The d__push_backup function always ensures the existence of the directory that is the immediate parent of the ORIG_PATH and of the generated backup path.

The d__push_backup function is intended to be used in conjunction with the d__pop_backup function.

The return codes are:

  • 0 — The ORIG_PATH has been made empty; and if anything existed there, it has been backed up.

  • 1 — The ORIG_PATH exists, but is inaccessible.

  • 2 — The backup path, whatever it is, is inaccessible or invalid.

  • 3 — Other unexpected error.

Table 35. List of d__push_backup options

-k, --keep-original

Directs to not vacate the ORIG_PATH. If something exists at that location, it is backed up. Otherwise, nothing is done.

Function d__pop_backup

The function d__pop_backup restores the latest backup of the given path to its original location. Whatever pre-exists at the original location is, in turn, backed up by appending the .bak suffix in-place.

d__pop_backup [-dep]… [--] ORIG_PATH [BACKUP_PATH]

If there are no backups at all, nothing is done and a success code is returned.

For the backup path, the first available strategy is employed:

  • If a BACKUP_PATH is given, it is used. If it turns out unusable, an error code is returned, without attempting other strategies.

  • If called from a deployment, the backup path is generated in the $D__DPL_BACKUP_DIR directory, and named by concatenating:

    • the MD5 checksum of the ORIG_PATH;

    • the .bak suffix.

  • If none of the above works, an error code is returned.

To find the latest backup, this function first checks the generated backup path, then repeatedly appends an incrementing numerical suffix (-1, -2, etc.). As soon as it hits a path that does not exist, the previous path is interpreted as the latest backup. These numerical suffixes are hard capped at 1000.

To back up whatever pre-exists at the ORIG_PATH, the .bak suffix is appended to the ORIG_PATH. If that path happens to be occupied, the same routine of incrementing numbers is applied.

The d__pop_backup function is intended to be used in conjunction with the d__push_backup function.

The return codes are:

  • 0 — The backup has been popped successfully, according to the options.

  • 1 — The ORIG_PATH is inaccessible.

  • 2 — The backup path, whatever it is, is inaccessible or invalid.

  • 3 — Other unexpected error.

Table 36. List of d__pop_backup options

-e, --evict

This option makes it an additional priority to ensure that anything that pre-exists at the ORIG_PATH is no longer there by the time this function successfully returns. This means that even if there is no backup to pop, the ORIG_PATH still gets vacated.

-d, --dispose

With this option, the function treats whatever pre-exists at the ORIG_PATH as disposable. In this mode, no backups of the ORIG_PATH are made.

-p, --precise

Directs to skip the motions of looking up the latest backup version by appending the suffixes. Instead, the backup location is used either precisely or not at all.

Function d__os_pkgmgr

The d__os_pkgmgr function is a thin wrapper around the OS package manager. The idea is to be able to install universally available system packages on any supported OS. On OS’s that are not yet supported, this function does nothing and returns non-zero.

d__os_pkgmgr update|has|check|install|remove [PKG-NAME]

Launches one of the five routines, which are expected of any package manager out there:

  • update — updates all installed packages (other arguments are ignored).

  • has — checks whether the PKG-NAME is available from that package manager.

  • check — checks whether the PKG-NAME is installed; returns zero/non-zero appropriately.

  • install — installs the PKG-NAME.

  • remove — uninstalls the PKG-NAME.

The PKG-NAME argument is the name of a single package; it is relayed to the underlying package manager verbatim. User prompts (except the prompt for sudo password) are suppressed/skipped.

Ways to contribute

You are welcome to contribute to Divine.dotfiles in any way you deem beneficial. This section is periodically updated to state the most sought after avenues of improvement.

Contributing OS support

One of the main goals of Divine.dotfiles is portability.

In Divine.dotfiles, the code that supports a particular OS distribution is concentrated in two locations:

  • The detection mechanism in the file at lib/procedures/detect-os.pcd.sh.

    In that file, search for the <<CONTRIBUTE HERE>> line.

  • The special files called adapters, located at lib/adapters/.

    To start the work on your own adapter, get the template at lib/templates/adapters/distro.adp.sh.

Adapters for all OS distributions out there will be welcomed with open arms.

Bundles

A bundle is a Github repository containing at least one deployment (or Divinefile). Nothing more is needed to make a bundle.

Bundle tag

A bundle may optionally carry a tag: a file in the root of the repository named bundle.sh. The bundle tag is the container for the bundle’s metadata, which is similar to the deployment metadata and pose as Bash variable assignments. Despite the .sh suffix, the bundle tag is never actually sourced by the Bash interpreter. Instead, the metadata values are extracted using pattern matching. The same syntax limitations should be adhered as for the deployment counterparts, except that the bundle metadata may occur anywhere within the tag file.s

Currently, the following metadata are supported:

Bundle versioning

The framework provides the transitions system. A transition is a Bash script that is sourced by the framework whenever the bundle reaches or supercedes a particular version.

Transition scripts may be put into the directory named transitions, located in the root of the bundle repository. A transition script must be named as the version of the bundle that it applies to, followed by the .trs.sh suffix. Multiple transitions are applied in the ascending alphanumerical order of their file names.

For example, a transition script transitions/1.5.0.trs.sh is sourced in one of two scenarios:

  • The bundle is attached with its version equal to or newer than 1.5.0.

  • The bundle is updated from a version older than 1.5.0 (or from an unmarked version) to a version equal to or newer than 1.5.0.

The framework monitors the return code of the transition script: if it is not zero, the transition is marked as failed. Whenever a transition fails:

  • no further transitions are applied;

  • on the next invocation of the update routine the transitions are re-applied starting from the failed one;

  • the bundle is excluded from the primary routines until all transitions are applied successfully.

Divine bundles

The Divine bundles of deployments are regular bundles that are developed and distributed alongside Divine.dotfiles. Apart from being useful by themselves, they serve as an illustration of the framework’s mechanisms put to good use.

The Divine bundles are published to their own Gighub organization, divine-bundles.

Table 37. List of Divine bundles

The deployment bundle essentials for Divine.dotfiles is a collection of opinionated system set-up scripts. It is intended as a useful starting point for any Unix-like box.

The effects of installing the essentials bundle are described on its page and also in the joy ride section.

Divine bundles in development

The following bundles are not yet in a satisfactory state for publishing. Still, these are functional to the described extent.

Table 38. List of Divine bundles in development

(fully functional on macOS, not yet fully documented)

Automates one-time setting-up of utilities that tend to require it:

  • GnuPG: automates adherence to best security practices while handling the secret keys.

  • SSH: automates secure stashing and retrieving of SSH keys.

(fully functional on macOS, not yet fully documented)

Automates plugging (and unplugging) of the pre-made configuration files for:

(fully functional on macOS & Ubuntu, not yet fully documented)

Maintains a collection of personal font files across machines.

macOS-specific deployments

(fully functional on macOS, not yet fully documented)

Programmatically switches the keys tilde ~ and plus-minus ± on a MacBook’s built-in physical keyboard.

(still unstable, not yet fully documented)

Maintains a local development server on macOS. For the most part, it automates instructions from this article.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].