As a beginner, Salt seemed daunting to me at first. It took me some efforts to learn but I love it now! I'm writing this guide for beginners who enjoy an hands-on introduction with examples.
Our journey starts with a file found in the base Salt configuration directory in dom0: /srv/salt/qubes/README.rst
(GitHub link). In this file we can read:
> ### qubes.user-dirs
>
> Install and maintain user salt and pillar directories for personal state configurations:
>
> txt
> /srv/user_salt
> /srv/user_pillar
>
>
> User defined scripts will not be removed on removal of qubes-mgmt-salt by design nor will they be modified on any updates, other than permissions being enforced.
We can activate qubes.user-dirs
to create personal state configuration directories. What is this, and how do we activate it? This is what we call a state configuration. It is a configuration file that tells Salt what to do to reach a particular state.
To activate qubes.user-dirs
, we can follow the instructions found in its configuration file, /srv/salt/qubes/user-dirs.sls
(GitHub link):
> txt
> qubes.user-dirs
> ===============
>
> Install and maintain user salt and pillar directories for personal state
> configurations:
>
> Includes a simple locale state file
>
> User defined scripts will not be removed on removal of qubes-mgmt-salt
> by design nor will they be modified on any updates, other than permissions
> being enforced.
>
> Execute:
> --------
> qubesctl state.sls qubes.user-dirs
>
We run the command sudo qubesctl state.sls qubes.user-dirs
. Salt applies the corresponding state, and tell us that some files and directories were created. Among these directories we can find /srv/user_salt/
: this is the main directory where we'll place our state configuration files.
Running the state qubes.user-dirs
will also create the file /srv/user_salt/top.sls
. Here is what this file looks like before we modify it (GitHub link):
> yaml
> # vim: set syntax=yaml ts=2 sw=2 sts=2 et :
> #
> # 1) Intial Setup: sync any modules, etc
> # --> qubesctl saltutil.sync_all
> #
> # 2) Initial Key Import:
> # --> qubesctl state.sls salt.gnupg
> #
> # 3) Highstate will execute all states
> # --> qubesctl state.highstate
> #
> # 4) Highstate test mode only. Note note all states seem to conform to test
> # mode and may apply state anyway. Needs more testing to confirm or not!
> # --> qubesctl state.highstate test=True
>
> # === User Defined Salt States ================================================
> #user:
> # '*':
> # - locale
>
This file is called the top file.
In the future, when we have many state configuration files, it will become quite tedious to run each state one by one with the command sudo qubesctl state.sls my-custom-state
. The top file solves that. If we write in this file how to run each state, we get the ability to run all of them with a single command: sudo qubesctl state.highstate
. We call this "running highstate".
There are three lines that are commented out at the end of the top file /srv/user_salt/top.sls
:
> yaml
> user:
> '*':
> - locale
>
If we were to uncomment those lines and run highstate, Salt would run in all targeted qubes (this is what is meant by the *
character) the state locale
, for which the state configuration file is either /srv/user_salt/locale.sls
or /srv/user_salt/locale/init.sls
.
How do we target a qube? By default, the commands qubesctl state.sls my-custom-state
and qubesctl state.highstate
only target dom0. To make Salt target additional qubes, we can give their names to the --targets
argument:
sudo qubesctl --targets=fedora-38 state.sls my-custom-state
will run my-custom-state
targeting dom0 and fedora-38.sudo qubesctl --skip-dom0 --targets=debian-12,untrusted state.highstate
will run highstate targeting the qubes debian-12 and untrusted but not dom0.We have a template called fedora-38. We would like Salt to create a purple qube named "salty" based on this template. We write the state configuration file /srv/user_salt/salty.sls
as follows:
salty--create-qube:
qvm.vm:
- name: salty
- present:
- template: fedora-38
- label: purple
- prefs:
- label: purple
That's it! Running sudo qubesctl state.sls salty saltenv=user
will make Salt create a purple qube named salty. If salty is already present, Salt will just make sure it's purple but won't do anything else.
[details=Note on saltenv=user
]
Note that we always need to add the extra argument saltenv=user
to the command sudo qubesctl state.sls my-custom-state
when we run individual states from the user directory /srv/user_salt/
.
[/details]
To make things easier, we would like to automatically run this state when we run highstate. We add the following to the top file /srv/user_salt/top.sls
:
user:
dom0:
- salty
Great! Now, the command sudo qubesctl state.highstate
will automatically create salty.
We have a template called debian-11. We would like Salt to create a green qube named "disconnected" based on this template, but that has no web browser and no internet access. We write the state configuration file /srv/user_salt/disconnected.sls
as follows:
{% set gui_user = salt['cmd.shell']('groupmems -l -g qubes') %}
disconnected--create-qube:
qvm.vm:
- name: disconnected
- present:
- template: debian-11
- label: green
- prefs:
- label: green
- netvm: none
- features:
- set:
- menu-items: 'org.gnome.Terminal.desktop org.gnome.Nautilus.desktop'
disconnected--update-app-menu:
cmd.run:
- name: 'qvm-appmenus --update disconnected'
- runas: {{ gui_user }}
- require:
- qvm: disconnected--create-qube
Perfect! We can now make Salt create this qube with the command sudo qubesctl state.sls disconnected saltenv=user
.
[details=Note on templating]
Note that by default, cmd.run
makes Salt run commands as root. The command qvm-appmenus
does not work as root, so we have to make Salt run this command as a regular user. To do so, in the first line of the file we use a templating language called jinja to retrieve our username, we save our username in the gui_user
variable, and we use this variable when needed. Salt will always execute all the templating instructions before running a state configuration file.
[/details]
To make things easier, we would like to automatically run this state when we run highstate. We add the following lines to the top file /srv/user_salt/top.sls
:
user:
dom0:
- disconnected
Great! Now, the command sudo qubesctl state.highstate
will also automatically create our disconnected qube.
I hope this was clear. Here are some links if you'd like to go further:
The next part of this guide will be about creating new templates and installing packages in them. See you soon!