The Bash framework for dotfiles and everything Bash
New machine or new OS?
Set it up with a single di install --yes
.
|
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.
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:
Dead simple. One wouldn’t need a framework for that. But there’s more.
The Grail directory at |
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 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 |
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. |
Queues and multitasking 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. |
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.
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.
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.
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.
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.
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:
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:
The location of the Grail directory can be permanently overridden by creating a file named |
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: |
The Divine intervention utility. This script is the command line interface to the framework. (The script file is, of course, maintained by the framework.) |
The shortcut command: a symlink to the framework’s main script, |
-
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
orwget
.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.
The following single shell command installs the Divine.dotfiles framework:
|
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: 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.
Prepend on the left |
---|
By default, the framework is installed into the |
By default, the shortcut command for the framework is named |
By default, the shortcut command for the framework is installed into one of the usual |
Append on the right |
Prompt options
Assumes an affirmative ( Other options notwithstanding, the |
Framework prompt options
Assumes an affirmative ( Other options notwithstanding, the |
Shortcut prompt options
Assumes an affirmative ( |
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 |
Verbosity options
Gradually increases ( |
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.
The following single shell command uninstalls the Divine.dotfiles framework:
|
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: 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.
Prepend on the left |
---|
By default, the framework is uninstalled from the |
Append on the right |
Prompt options
Assumes an affirmative ( Other options notwithstanding, the |
Framework prompt options
Assumes an affirmative ( Other options notwithstanding, the |
Optional dependency prompt options
Assumes an affirmative ( |
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 |
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 |
Verbosity options
Gradually increases ( |
Note, that no combination of options guarantees an unattended uninstallation of the framework. As established before, Divine.dotfiles is intended as an interactive tool.
The following single shell command provides the introductory experience of the framework and deployments:
-
installs the framework;
-
attaches the
essentials
bundle of Divine deployments (distributed separately); -
installs the attached deployments.
All three sub-commands skip the trivial prompts (e.g., 'Are you sure?').
|
(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.
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/
.
essentials
Custom assets for the Bash-it shell framework. |
The Brewfile, maintained on macOS. |
The global configuration for Git. |
The startup scripts (runcoms) for Bash and Zsh. |
The global configuration for Vim. |
The file |
Custom assets for the oh-my-zsh shell framework. |
The container for the personal executables (this directory is maintained on the |
The dagger mark (†) meaning: in order for the modifications in that asset directory to take effect, the deployment must be (re-)installed. |
The following single shell command methodically undoes the joy ride installation:
-
removes (uninstalls) the attached deployments;
-
detaches the
essentials
bundle; -
uninstalls the framework.
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.
|
After the undo steps have successfully run, there is no trace of Divine.dotfiles on the system. (Sigh.)
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:
The primary routines, operating on deployments: |
The bundle routines, operating on Github repositories: |
The routine that wrangles the Grail directory:
|
The routine that brings what it can up to date:
|
The term 'deployments' includes Divinefiles as a special kind. |
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.
-
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.
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.
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.
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.
For the optional NAME
arguments, the following (case-insensitive) values are accepted:
-
Names of deployments.
-
Reserved synonyms for Divinefiles:
divinefile
,dfile
,df
. -
Single-digit names of deployment groups:
0
,1
,2
,3
,4
,5
,6
,7
,8
,9
.
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 search can be narrowed down to particular bundles of deployments by including any number of the --bundle
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.
Assumes an affirmative (
Examples of the urgent prompts, which are not affected by the prompt options:
Other options notwithstanding, the The status of the prompt options is reflected in the value of the
|
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 The presence of the
|
The framework adheres to the non-interference policy. During the The The presence of the
|
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 The list of requested bundles (if any) is reflected in the items of the |
The primary routines interpret the optional The presence of the
|
The primary routines interpret the dangerous deployments (marked with the The presence of the
|
Gradually increases ( Every instance of the The The amount of output per the verbosity level can be described as such:
The global verbosity level is reflected in the value of the |
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:
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 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.
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_.-]+$
.)
Prompt options
Assumes an affirmative (
Other options notwithstanding, the |
Verbosity options
Gradually increases ( |
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
Prompt options
Assumes an affirmative (
With the Other options notwithstanding, the |
The framework adheres to the zero data loss policy. Accordingly, during the The |
Normally, the The |
Verbosity options
Gradually increases ( |
There are three parts of a Divine.dotfiles installation that are potentially cloned Git repositories:
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
Prompt options
Assumes an affirmative (
Other options notwithstanding, the |
If at least one such option is included:
The same values are accepted for the |
Verbosity options
Gradually increases ( |
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:
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:
-
Implementations of any or all of the primary functions.
-
Assignments to the special pseudo-variables — the metadata.
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.
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.
-
If this function is implemented, it will be called:
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.
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.
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 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.
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
, ordf
. -
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.
D_DPL_PRIORITY=777
The priority is the way to order the deployments for processing:
The priority must be a non-negative integer, otherwise it falls back to the default value of 4096
.
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.
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. |
Marks the deployment as dangerous. The framework ignores dangerous deployments, unless explicitly told not to. |
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. |
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.
# 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 D
OS_DISTRO
indicators.
A match against any of the two is sufficient.
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.
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.
d_dpl_check
function
|
Meaning |
Sub-statuses |
Actions during the primary routines |
|||||
---|---|---|---|---|---|---|---|---|
Relevant? |
Stash record? |
Appears installed? |
||||||
Regular |
Regular |
|||||||
|
'Truly unknown' |
Yes |
unused |
unknown |
Push backup; install |
Uninstall; pop backup |
||
|
'Fully installed' |
Yes |
Yes / unused |
Yes |
blocked |
Push backup; install anew —or— Bring up to date |
Uninstall; pop backup; clear stash |
|
|
'Fully not installed' |
Yes |
No / unused |
No |
Push backup; install; set stash |
blocked |
Pop backup |
|
|
'Irrelevant or invalid' |
No |
n/a |
blocked |
||||
|
'Partly installed' |
Yes |
Yes / unused |
Partly |
Make us whole |
Push backup; install anew |
Uninstall; pop backup; clear stash |
|
|
'Likely installed (unknown)' |
Yes |
Yes |
unknown |
blocked |
Push backup; install anew —or— Bring up to date |
blocked |
Uninstall; pop backup; clear stash |
|
'Manually removed (tinkered with)' |
Yes |
Yes |
No |
blocked |
Push backup; install anew |
blocked |
Clear stash |
|
'Fully installed (by user or OS)' |
Yes |
No |
Yes |
blocked |
Push backup; install anew; set stash |
blocked |
Uninstall; pop backup |
|
'Partly installed (by user or OS)' |
Yes |
No |
Partly |
Make whole; set stash |
Push backup; install anew; set stash |
blocked |
Uninstall; pop backup |
|
'Likely not installed (unknown)' |
Yes |
No |
unknown |
blocked |
Push backup; install; set stash |
blocked |
Pop backup |
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.
d_dpl_install
function
|
Meaning |
Elaboration |
---|---|---|
|
'Successfully installed' |
The function has run without any errors. |
|
'Failed to install' |
There has been at least one error, which potentially created an inconsistent state. |
|
'Refused to install' |
The function has returned before proceeding to the installation proper. |
|
'Partly installed' |
The function has returned midway (because of an error), but has avoided creating an inconsistent state. |
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.
d_dpl_remove
function
|
Meaning |
Elaboration |
---|---|---|
|
'Successfully removed' |
The function has run without any errors. |
|
'Failed to remove' |
There has been at least one error, which potentially created an inconsistent state. |
|
'Refused to remove' |
The function has returned before proceeding to the removeation proper. |
|
'Partly removed' |
The function has returned midway (because of an error), but has avoided creating an inconsistent state. |
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.
Add-statuses in primary functions |
---|
If set to Naturally, this add-status only makes sense when set in the |
If set to |
Output add-statuses
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.
Other than printing the styled message, these add-statuses do nothing. |
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.
This indicator contains the integer check code returned by the |
These indicators describe the current operating system as detected by the built-in framework mechanisms.
All are declared
|
These indicators contain the absolute paths to the deployment’s special directories.
All are declared |
These indicators contain the absolute paths to the deployment’s special files.
All are declared
|
Option indicators
The option indicators reflect the run-time state of the options, provided to the primary routine. The option indicators are declared
|
Request indicators
The request indicators reflect the parameters of the current request to the primary routine. The request indicators are declared
|
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.
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.
entry1
entry2
entry with whitespace
indented entry will not include indentation
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.
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.
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.
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.
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.
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
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.
(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.
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.
The asset manifests are used to:
-
Catalog the deployment’s assets.
-
Copy the provided initial versions of assets into the asset directory.
-
Automatically assemble queues from the asset paths.
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).
Processing of the asset manifests occurs:
-
During the primary routines, immediately before sourcing the deployment script.
-
After attaching a bundle.
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:
Behavior without the flag (default) | Asset flag | Behavior with the flag |
---|---|---|
The entry is interpreted as a concrete path to a single asset. |
|
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. |
|
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. |
|
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. |
|
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 This flag should be used sparingly! |
Paths to matching assets are pushed onto the asset arrays for further usage. |
|
Paths to matching assets are not pushed onto the asset arrays. Together with the |
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 |
|
The asset entry is considered limited to those matching assets, for which the author has provided the initial versions. Irrelevant when the |
On top of the flags, the following key-values are recognized in asset manifests:
The |
This key-value does not affect the actual asset entries, nor does it support any values other than the pre-defined word |
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.
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
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.
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:
-
The Divinefiles collectively act as a deployment.
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.
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.
By default, if the package is not found to be installed via the system package manager, but is nevertheless available on |
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. |
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.
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
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.
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 thed_dpl_check
function. -
d_TASK_install
— the task-level equivalent of thed_dpl_install
function. -
d_TASK_remove
— the task-level equivalent of thed_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:
|
# 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() { :; }
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.
-
-
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.
Add-statuses in multitask deployments |
---|
If set to If set during the |
If set to |
Add-statuses of pre- and post- multitask hooks
These add-statuses allow to override the corresponding code of the helper primary from either the pre- or post- multitask hook. |
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 |
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.
These indicators contain the current task’s ordinal number ( |
During the Possible values: |
This indicator contains whatever flags might have been assigned to the current task. |
Task code indicators
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 |
Multitask code indicators
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 |
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:
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.
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 thed_dpl_check
function. -
d_item_install
— the queue item equivalent of thed_dpl_install
function. -
d_item_remove
— the queue item equivalent of thed_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:
|
# 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() { :; }
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:
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.
-
-
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.
Add-statuses in queues |
---|
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. |
If set to If set during the |
If set to |
Add-statuses of pre- and post- queue hooks
These add-statuses allow to override the corresponding code of the helper primary from either the pre- or post- queue hook. |
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 |
The queues can take advantage of the deployment-level indicators. On top of that, the framework provides indicators that are specific to the queues.
These indicators contain the current queue item’s ordinal number ( |
During the Possible values: |
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
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 |
Queue code indicators
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 |
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.
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.
-
Before the deployment script is sourced, an auto-queue is built from the deployment’s asset manifest:
-
D_QUEUE_MAIN
— populated with the relative paths to the assets. -
D_QUEUE_ASSETS
— populated with the absolute versions thereof. This array is used by the copy-queue and the link-queue variations.
This kind of auto-queue can be handily complemented with the auto-targeting.
-
-
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:
-
D_QUEUE_MAIN
— populated with the manifest entries.
-
An auto-queue is only built if the corresponding manifest exists. Both types of auto-queues support splitting the queue (via the manifest syntax).
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.
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.
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.
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; }
# ~/.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" )
}
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:
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
|
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.
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; }
# ~/.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" )
}
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.
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.
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.
# ~/.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" )
}
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.
Divine.dotfiles offers mechanisms that facilitate creation of better, stronger, faster deployments.
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:
-
D_NAME
— the variables to be assigned, e.g., the metadata and the add-statuses. -
d_name
— the functions to be implemented, e.g., the primary functions.
-
-
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., thed__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 functions:
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:
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:
-
Critical commands could be wrapped in the
d__cmd
,d__pipe
, ord__require
calls. -
'Manual' failures should be orchestrated via the
d__fail
function.
-
-
To interact with the user along the way:
-
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.
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
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 theDESCRIPTION
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 apop
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.
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 aDESCRIPTION
. -
3
— Stack not modified: popping from an empty stack. -
4
— Stack not modified: notching at the same position.
d__context
options
Sets a custom title for the debug message.
The title is not a part of the |
When this option is given: if the function produces any output, an extra newline is prepended to it. |
Quiet options
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:
However, if none of the quiet options are given, the default quiet levels per operation are:
|
Styling options
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:
These options are not combined and the last option given wins. |
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.
d__notify
(without any arguments)==> TITLE
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.
d__notify
options
Directs to print the notification only if the caller lacks the With this option, new defaults are in effect:
|
Sets a custom title for the notification. Without this option, the
|
When this option is given: if the function produces any output, an extra newline is prepended to it. |
Quiet options
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:
However, if none of the quiet options are given, the default quiet level is |
Context options
Includes the context block in the output with the following content:
These options are not combined and the last option given wins. |
Styling options
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:
These options are not combined and the last option given wins. |
Special para-options
The para-options work only to the right of the option-argument separator ( The para-options are shorthand for linebreaks, indentation, and bold titles.
|
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 and1
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
.
d__prompt
(without any arguments)PROMPT KEYS
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 toProceed?
. -
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 toUser 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.
d__prompt
options
Prompting modes
The prompting mode:
These options are not combined and the last option given wins. |
In both prompting modes, provides an extra option: 'quit'.
The returned value for the 'quit' option is always |
Sets a custom title for the leading line. Without this option, the
|
Sets a custom |
In the decision mode:
In the any-key mode:
Passing the value of the |
When this option is given: if the function produces any output, an extra newline is prepended to it. |
Context options
Includes the context block in the output with the following content:
These options are not combined and the last option given wins. |
Styling options
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:
These options are not combined and the last option given wins. |
Special para-options
The para-options work only to the right of the option-argument separator ( The para-options are shorthand for linebreaks, indentation, and bold titles.
|
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'.
d__fail
(without any arguments and with empty context stack)==> TITLE
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.
d__fail
options
Sets a custom title for the failure message. Without this option, the
|
When this option is given: if the function produces any output, an extra newline is prepended to it. |
Special para-options
The para-options work only to the right of the option-argument separator ( The para-options are shorthand for linebreaks, indentation, and bold titles.
|
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 WORD
s.
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
, orcase
, or the arithmetic context.
The return codes are:
-
0
— TheCMD
returned zero (after the optional negation). -
1
— TheCMD
returned non-zero (after the optional negation). -
2
— Called without arguments.
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.
d__cmd
options
Stops processing options for the wrapper function, and reads the rest of the arguments as parts of the |
Negates the return code of the |
Output suppression modes
These options are not combined and the last option given wins. |
Quiet options
The quiet options designate the quiet level for the current call.
The 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:
However, if none of the quiet options are given, the default quiet level is |
Semantics of failure |
---|
Makes the When a command is marked optional, its failure output is printed depending on the quiet level. |
Sets a custom title for the failure output. |
Sets a custom circumstances description for the failure output. |
Sets a custom consequences description for the failure output. |
Label option
The Normally the failure output prints the offending For example, the command
|
Label backreference options
A previously assigned label may be re-used if the |
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.
d__pipe
options (on top of those in d__cmd
)
Pipe option
Inserts the Bash’s pipe operator ( |
Commands in the pipe are numbered starting from zero, left to right.
If this option is given, the Without this option, the last command in the pipe is used. |
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.
d__require
options (on top of those in d__cmd
)
And/or options (only the first two are recognized) |
---|
And/or options
|
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 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.
d__stash
Returns |
Ensures that the stash contains a single instance of the Returns |
Adds one instance of the Returns |
Prints the value of the first instance of the Returns |
Prints each value of the Returns |
Returns |
Prints each Returns |
Clears all records from this stash. |
If called with an unsupported routine name, the function prints an error and returns |
d__stash
options
By default, any inconsistency during stashing triggers a loud error message.
The This option is intended for cases when not using the stash is also a viable option. |
Stash level options
These options are not combined and the last option given wins.
|
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.
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.
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
— TheORIG_PATH
has been made empty; and if anything existed there, it has been backed up. -
1
— TheORIG_PATH
exists, but is inaccessible. -
2
— The backup path, whatever it is, is inaccessible or invalid. -
3
— Other unexpected error.
d__push_backup
options
Directs to not vacate the |
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
— TheORIG_PATH
is inaccessible. -
2
— The backup path, whatever it is, is inaccessible or invalid. -
3
— Other unexpected error.
d__pop_backup
options
This option makes it an additional priority to ensure that anything that pre-exists at the |
With this option, the function treats whatever pre-exists at the |
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. |
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 thePKG-NAME
is available from that package manager. -
check
— checks whether thePKG-NAME
is installed; returns zero/non-zero appropriately. -
install
— installs thePKG-NAME
. -
remove
— uninstalls thePKG-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.
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.
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:
Adapters for all OS distributions out there will be welcomed with open arms.
A bundle is a Github repository containing at least one deployment (or Divinefile). Nothing more is needed to make a bundle.
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:
-
D_BUNDLE_VERSION=MAJOR.MINOR.PATCH
The current version of the bundle, following the semantic versioning pattern. By default, this version is purely cosmetic, but can play a crucial part in the 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 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.
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
.
The deployment bundle |
The following bundles are not yet in a satisfactory state for publishing. Still, these are functional to the described extent.
(fully functional on macOS, not yet fully documented) Automates one-time setting-up of utilities that tend to require it: |
(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 |
(still unstable, not yet fully documented) Maintains a local development server on macOS. For the most part, it automates instructions from this article. |