Caerbannog makes it easy to write declarative Python scripts to automatically configure your local user accounts.
First, create a configuration script to describe the various types of devices on which you want to configure your user account:
./configure.py:
from caerbannog.prelude import *
target("laptop").depends_on("windows")
target("desktop").depends_on("archlinux")
target("work-laptop").depends_on("windows").has_roles("vscode")
target("windows").depends_on("common").has_roles("powershell", "windows-terminal")
target("archlinux").depends_on("common").has_roles("zsh", "bspwm", "vim")
target("common").has_roles("git")
commit()Now create roles which describe applications or configurations that you want to
deploy. For instance, for git, you may want to install the package and create
a .gitconfig and global .gitignore in your home directory:
roles/git/role.py:
from caerbannog.roles.prelude import *
def configure():
Do(
Package("git").is_installed(),
File(home_dir(".gitconfig")).has_template("gitconfig.j2"),
File(home_dir(".gitignore")).has_lines(".vscode"),
)roles/git/gitconfig.j2:
[core]
hooksPath = "~/.githooks"
[user]
name = Edsger Dijkstra
email = dijkstra@novigrad.rd
[fetch]
prune = true
[pull]
rebase = true
Now run the script to bring your machine to its desired state:
$ python configure.py apply laptop
All actions that are taken and changes that are made are clearly reported:

Caerbannog allows you to write code that specifies what your system should look
like. For instance, you can declare that the file .gitignore in your home
directory has .vscode as its content in the following manner:
File(home_dir(".gitignore")).has_lines(".vscode")If the file exists in your home directory, and it has the content you specified, no action will be taken. If it is not present, it will be created. If it exists, but its content differs, it will be modified.
To keep related assertions together, you can group them by creating roles, and
placing all related assertions within those roles. In this case, it makes sense
to create a git role which also asserts that your .gitignore has the right
content, and that the git package is installed.
Often, you will want to configure multiple devices, and not always all in the same way. To allow for this, you can create targets. By assigning roles to targets, you can selectively apply roles to different devices.
These targets are specified in the configure.py script.
target("laptop").has_roles("git")Now, if you run the script, you can apply the git role by selecting the
laptop target:
python ./configure.py apply laptop
Caerbannog is intended to be easy to extend, which is done by writing plugins or by creating new subjects.
Plugins can be used to execute custom logic to set contextual information. For instance, you may want to read some system parameters and write these to global variables to make them available from your templates. This is easily done with a plugin.
Adding new subjects will allow you to make new types of changes to your system.
By default, Caerbannog provides subjects such as File and Directory (to
create, remove, or update files and directories), Package (to install or
update packages using the system package manager), and SystemdService (to
control services with systemd).
Subjects are classes inheriting from Subject. They expose public methods to
let the user describe what should happen to this subject, e.g.
File(.gitignore).has_content("__pycache__"). When these methods are executed,
they will then register assertions, which describe desired properties for that
subject. For instance, calling has_content('text') on a file implies two
assertions for that file:
- The file exists.
- The content of the file is
text.
These are represented by the IsFile and HasContent assertions. Assertions
can be created by inheriting from Assertion, and must implement an apply()
method. When executed, this method should check if the assertion holds, and if
it does not, it should make the necessary changes to the system in order to make
that assertion true. Using HasContent as an example, the assertion will:
- Read the file.
- Compare its current content against its desired content.
- If both match, no action is taken.
- If they do not match, a change is registered to write the desired content to the file.
An assertion itself should not directly make changes to the system. Instead,
it should create an instance of a class deriving from Change, which holds all
information necessary to perform the change. Changes can be queued by calling
register_change().