Tutorial¶
Writing an application configuration template¶
Motivation¶
In order to use an application on a UNIX system, there are often several tasks that need to be performed before doing so.
- Determine if the application should be installed on the system in the first place.
- Install the package.
- Place the default configuration file for the application in its appropriate location.
- Tweak configuration paramateres according to the host environment and personal preferences.
- Start any required background processes on startup.
Preferably you would want to do this work once, and easily deploy such configurations across different systems. Astrality enables you to manage such tasks in a reproducible and sharable way, grouping those tasks together into a single configuration.
For the sake of example, we will use Astrality to manage the configuration of polybar, a status line application for Linux. Hopefully, we will be able to demonstrate that the end result is a configuration that is easy to tweak, re-deploy, and share with others.
The task¶
In most cases, you will have an existing configuration to start from. In this case it will be the default configuration shipped with polybar. Here is a small extract of this default configuration file:
[bar/example]
;monitor = ${env:MONITOR:HDMI-1}
font-0 = fixed:pixelsize=10;1
font-1 = unifont:fontformat=truetype:size=8:antialias=false;0
font-2 = siji:pixelsize=10;1
[module/wlan]
type = internal/network
interface = wlp3s0
interval = 3.0
This extract contains the two types of configuration types one usually wants to change:
- Personal preference - The three main fonts used in the status bar.
- Host environment - The wlan interface identifier.
Create a polybar module¶
We will create a Astrality module which is responsible for the management of all things related polybar.
Modules can by defined in either ~/.config/astrality/modules.yml
or
~/.config/astrality/modules/<module_group>/modules.yml
.
You can tweak these locations to your preferences by setting
$ASTRALITY_CONFIG_HOME and/or by setting the
modules_directory config option.
For now, we will create a statusbars
module group in the latter default
location.
Let’s start by creating a seperate directory for this module group:
$ mkdir -p ~/.config/astrality/modules/statusbars && cd $_
We will also move the default configuration file to this folder to keep
everything in one place.
This file will be used as a template for compilation so we will prefix the
filename with template.
to make this clear:
$ mv ~/.config/polybar/config template.config
We will define a module named polybar
which
compiles this template to the previous location:
polybar:
compile:
content: "template.config"
target: "~/.config/polybar/config"
You can also instruct Astrality to copy or symlink, optionally recursively. See the actions documentation for more information.
We can now compile this template by running: astrality -m
statusbars::polybar
, or alternatively just astrality
, as all defined
modules are enabled by default. An optional --dry-run
flag is supported
if you want to safely check which actions will be executed.
At this point this is nothing more than a glorified copy script, but we can now start to insert Jinja2 templating syntax into this file.
Writing the template with context placeholders¶
We can start by defining some context values which we want to insert into our template:
statusbar:
font:
size: 16
1: "FuraCode Nerd Font"
2: "FuraCode Mono Nerd Font"
host:
interfaces:
wlan:
handle: "wlp3s0"
ethernet:
handle: "eno0"
And now let’s use placeholders in the template where these values should be inserted:
[bar/example]
;monitor = ${env:MONITOR:HDMI-1}
font-0 = {{ statusbar.font.1 }}:pixelsize={{ statusbar.font.size }};1
font-1 = {{ statusbar.font.2 }}:fontformat=truetype:size={{ statusbar.font.size }}:antialias=false;0
font-2 = {{ statusbar.font.3 }}:pixelsize={{ statusbar.font.size }};1
[module/wlan]
type = internal/network
interface = {{ host.interfaces.wlan.handle }}
interval = 3.0
The compilation target will replace these placeholders with the placeholders
defined in context.yml
, and you can check the result by running astrality
-m statusbars::polybar
again.
This extracts the configuration options which are of interest into a much more succinct file, enabling us to tweak it easily. The same placeholders can be used in other templates, which can make switching between different status bars more consistent, for instance. There are also other benefits related to sharing modules, which we will come back to later.
Hint
You may have noticed that we only defined two fonts in context.yml
,
while using three fonts in the template, thinking that the use of {{
statusbar.font.3 }}
is undefined. But for numeric context keys, astrality
will fall back to the greatest number available.
With other words: statusbar.font.3 -> statusbar.font.2
.
This allows us to specify an additional font in the future if we want to.
More information can be found in the templating documentation.
Expanding the module¶
Starting polybar¶
Currently, Astrality only compiles the template and quits. We can do better! First, we can instruct Astrality to start polybar after having compiled the template:
polybar:
compile:
content: "template.config"
target: "~/.config/polybar/config"
run:
shell: "polybar --config=~/.config/polybar/config example"
We can now compile the template and start polybar by running astrality -m
statusbars::polybar
.
When using the --config
polybar flag, we do not actually care exactly where
the compiled template is saved, as long as we can provide the compilation path
to polybar. We can therefore skip specifying the target
for the compilation
and instead use {template.config}
in the shell command. This placeholder
will be replaced with the file path to the compiled template.
polybar:
compile:
content: "template.config"
run:
shell: "polybar --config={template.config} example"
This will reduce additional clutter on our filesystem and prevent overwriting any existing files. This unique compilation target will make the module easier to share with other, a topic which we will come back to soon.
We can also kill potentially existing polybar processes before starting the new one:
polybar:
compile:
content: "template.config"
run:
- shell: "killall -q polybar"
- shell: "polybar --config={template.config} example"
See the run action for more information regarding the execution of shell commands within Astrality.
Requirements¶
By default, all modules defined in subdirectories of
~/.config/astrality/modules
will be enabled. See enabled modules
documentation for how to gain more fine-grained
control.
We can add additional constrains for when we consider a module enabled. In this case, we can require polybar to be installed on the system. If polybar is not installed, Astrality will skip any further module action and log a warning.
polybar:
requires:
installed: polybar
compile:
content: "template.config"
run:
- shell: "killall -q polybar"
- shell: "polybar --config={template.config} example"
You can also add requirements related to environmet variables, shell command
exit codes, and other astrality modules. Alternatively, you can define actions
within an on_setup
block to install such dependencies once, and only once.
See module dependencies and action blocks for more information.
Sharing your module¶
Publish to GitHub¶
You can easily share an Astrality module by publishing the module directory to
GitHub as a repository. modules.yml
and context.yml
must be located
at the root level of the repository. An example module repository can be found
here.
Fetch module from GitHub¶
Let us assume that your GitHub username is username
and you published the
statusbars module directory as part of a repository named statusbars
.
Other people can now try your status bar configuration by running:
$ astrality -m github::username/statusbars::polybar
Astrality will automatically clone the repository and execute the module’s actions. They will be able to quickly judge if your specific configuration is to their taste or not.
Overriding context values¶
The context values used in the polybar template was earlier defined in
context.yml
within the module directory. Any such context key can
be overwritten by defining the same key in the global context store
located at ~/.config/astrality/context.yml
.
This allows anybody to specify their favorite font and correct WLAN interface handle, while still using your polybar configuration.
It is this that makes Astrality modules much more sharable across
different preferences and host environments. The context.yml
clearly stipulates which parameters which someone (including you)
probably want to change at some time.
If someone want specify their correct interfaces, while keeping your specified font, they can define the following global context items (all without taking a deep-dive into your configuration):
host:
interfaces:
wlan:
handle: "wlp3s1"
ethernet:
handle: "eno2"
Permanently add a GitHub module¶
If the user decides to keep the module in use, they can add
github::username/statusbars::polybar
to the enabled_modules
section in
~/.config/astrality/astrality.yml
. It will be added to any existing modules
when executing astrality
in the shell.
Clean up files created by a module¶
You can easily clean up any files created by a module of any type, restoring
any overwritten files in the process. Use the --cleanup
flag with the
same name you would use to enable the module. For example:
$ astrality --cleanup github::username/statusbars::polybar
If a module has overwritten a valuable file, you can use this option to restore
it. It also makes it easy to remove configuration files for applications you no
longer use! You can also try a new module with the --dry-run
flag to safely
check which actions that will be executed.
Managing dotfiles with templates¶
It is relatively common to organize all configuration files in a “dotfiles” repository. How you structure such a repository comes down to personal preference. We would like to use the templating capabilities of Astrality without making any changes to our existing dotfiles hierarchy. This is relatively easy!
Let us start by managing the files located in $XDG_CONFIG_HOME
, where most
configuration files reside. The default value of this environment variable is
“~/.config”. We will create an Astrality module which automatically detects
files named “template.whatever”, and compile it to “whatever”. This way you can
easily write new templates without having to add new configuration in order to
compile them.
# ~/.config/astrality/modules.yml
dotfiles:
compile:
content: $XDG_CONFIG_HOME
target: $XDG_CONFIG_HOME
include: 'template\.(.+)'
Let us go through the module configuration step-by-step:
- We use the
compile
action type, as we are only interested in compiling templates at the moment. - We set both the content and target to be
$XDG_CONFIG_HOME
, compiling any template to the same directory as the template. - We only want to compile template filenames which matches the regular
expression
template\.(.+)
. - The regex capture group in
template\.(.+)
specifies that everything appearing after “template.” should be used as the compiled target filename.
We can now compile all such templates within $XDG_CONFIG_HOME by running
astrality
from the shell. Before doing so, it is recommended to run
astrality --dry-run
to see which actions that will be performed.
But we would like to automatically recompile
templates when we modify them or create new ones. You can achieve this by
enabling reprocess_modified_files
in astrality.yml
:
# ~/.config/astrality/astrality.yml
config/modules:
reprocess_modified_files: true
Astrality will automatically recompile any modified templates as long as it runs as a background process.
Let us continue by managing a more complicated dotfiles repository. Most people
create a separate repository containing all their configuration files, not
only $XDG_CONFIG_HOME
. The repository is then cloned to something like
~/.dotfiles
, the contents of which is symlinked or copied to separate
locations, $HOME
, $XDG_CONFIG_HOME
, $/etc
on so on. You can do all
of this with Astrality.
For demonstration purposes, let us assume that the templates within “~/.dotfiles/home” should be compiled to “~”, and “~/.dotfiles/etc” to “/etc”, while non-templates should be symlinked instead. This combination of symlink and compile actions can be done with the stow action.
Move modules.yml
and astrality.yml
to the root of your dotfiles
repository. Set export ASTRALITY_CONFIG_HOME=~/.dotfiles
. Finally, modify
the dotfiles module accordingly:
# ~/.dotfiles/modules.yml
dotfiles:
stow:
- content: home
target: ~
templates: 'template\.(.+)'
non_templates: symlink
- content: etc
target: /etc
templates: 'template\.(.+)'
non_templates: symlink
templates: 'template\.(.+)'
and non_templates: symlink
are actually the
default options for the stow action, so we could have skipped specifying them
altogether. Alternatively, you can specify non_templates: copy
.
You can now start to write all your configuration files as templates instead, using placeholders for secret API keys or configuration values that change between machines, and much much more.
A module using events¶
Let us explore the use of events
with an example: we want to use a different desktop wallpaper for each day of the week.
The weekday
event listener type keeps track of the following events: monday
, tuesday
, wednesday
, thursday
, friday
, saturday
, and sunday
.
After having found seven fitting wallpapers, we name them according to the weekday we want to use them, and place them in $ASTRALITY_CONFIG_HOME/modules/weekday_wallpaper/
:
$ ls -l $ASTRALITY_CONFIG_HOME/modules/weekday_wallpaper
monday.jpeg
tuesday.jpg
wednesday.png
thursday.tiff
friday.gif
saturday.jpeg
sunday.jpeg
Now we need to create a module with a weekday
event listener in modules.yml
:
weekday_wallpaper:
event_listener:
type: weekday
We also need a way of setting the desktop wallpaper from the shell. Here we are going to use the feh shell utility. Alternatively, on MacOS, we can use this script. After having installed feh
, we can use it to set the appropriate wallpaper on Astrality startup:
weekday_wallpaper:
event_listener:
type: weekday
on_startup:
run:
- shell: feh --bg-fill modules/weekday_wallpaper/{event}.*
Now Astrality will set the appropriate wallpaper on startup. We still have a small bug in our module. If you do not restart Astrality the next day, yesterday’s wallpaper will still be in use. We can fix this by changing the wallpaper every time the weekday changes by listening for the weekday event.
weekday_wallpaper:
event_listener:
type: weekday
on_startup:
run:
- shell: feh --bg-fill modules/weekday_wallpaper/{event}.*
on_event:
run:
- shell: feh --bg-fill modules/weekday_wallpaper/{event}.*
Or, alternatively, we can just trigger the on_startup
action block when the event changes:
weekday_wallpaper:
event_listener:
type: weekday
on_startup:
run:
- shell: feh --bg-fill modules/weekday_wallpaper/{event}.*
on_event:
trigger:
- block: on_startup