Link On Boot: A better way to configure software with Salt Template States?

Hi everyone,

I am monscodex, creator of saltqubes.

I have been working intermittently on the implementation of an idea for Salt Template configurations. I believe that the idea has reached a certain level of maturity, and am seeking to collaborate with other creators of Salt configurations (for Qubes Templates) to finish polishing it. I am unaware whether what I am describing has been already been developed, if so, please refer to me.

Idea

Enable a Salt Template State to change configuration files for the AppVM qubes that use those Templates. Example: in a Template template-coding that installs the neovim package, provide a way to change the configuration files located at ~/.config/nvim in the AppVMs that use that Template.

This should be highly practicality as it restores a conceptual model we usually have for the distribution of software (that has been somewhat disrupted by the separation of Templates and AppVMs in Qubes): a Linux distribution, in simple terms, provides a way to install packages, a default set of installed packages and a default configuration for those installed packages.

In practical terms, there is a problem with our Qubes Salt configurations: they often split into two states: one applied to the Template (that installs the software) and one applied to the AppVMs (that configures the software), providing extra overhead and complexity by breaking the previously described model.

Another way I have tried to unify the installation and configuration is by changing the location of the configuration (for example, setting an environment variable to tell neovim to find its configuration at a certain path). The problem with such methods is that not every software supports it.

In my opinion, the following idea should only be carried forward if it provides a safe implementation (defined below).

Main Implementation (in Salt Template configuration)

  1. Copy dotfiles into a Template directory (e.g. /usr/local/saltqubes/link_on_boot/nvim)
  2. On every boot, link the usual configuration path to its new location (e.g. /home/user/.config/nvim → /usr/local/saltqubes/link_on_boot/nvim)

Voilà

Safety considerations

  1. If the source exists, create a backup of the existing content (e.g. if, in the AppVM, ~/.config/nvim already has a configuration, it should be moved to ~/.config/nvim.saltqubes.link_on_boot.previous)
  2. If the Template state is removed (e.g. I had run a Salt configuration that did the link on boot of the neovim config, and I no longer want to have a linked ~/.config/nvim), the link should be restored to its original content
  3. If the AppVM’s template is changed, it should remove the previous links. (e.g. moving the AppVM from template-coding (that links on boot a neovim configuration) to template-browser (that does not contain said configurations) should remove the

Implementation after safety considerations

The implementation is divided in the following parts (numbered in order of execution).

3. Linking

This is very similar to the principal implementation (1. the Salt state copies the files to a path in the root Template directory - 2. create a systemd system unit that links on boot), except for additional aspects:

  • Every time a link is made, the source path is added to a certain path (e.g. when linking from ~/.config/nvim to /usr/local/..., ‘/home/user/.config/nvim’ should be added to the file /home/user/.local/share/saltqubes/link_on_boot/links_from_previous_boot. It is important that this file is in the home directory as it needs to be stored in the AppVM)
  • All the linking services should depend on the unlinking service

2. Unlinking

There should be a systemd service that unlinks all the links created during the previous boot. These links are found in the /home/user/.local/share/saltqubes/link_on_boot/links_from_previous_boot file. When unlinking a path (e.g. ~/.config/nvim), if a backup file is found (e.g. ~/.config/nvim.saltqubes.link_on_boot.previous), it should be moved back to the corresponding path (e.g. ~/.config/nvim).

It is important that the systemd service is a user service stored in ~/.config/systemd, because if the Template is changed, it still needs to run on the next boot.

1. Creating the unlinking service

Because the unlinking service needs to be located in the AppVM’s home directory, it cannot be managed directly by the Salt state applied to the Template directly.

To solve this issue we can create a systemd system unit that will create and enable the unlinking service (yes, a system service that creates and enables a user service).

Case analysis

In theory, we could leave everything as is, so that on every boot:

  1. If the unlinking user service does not exist the unlinking-service-creator system service will create it
  2. The unlinking service runs and unlinks every link from the previous boot
  3. Linking for the new boot takes place

If for an AppVM (e.g. coding based on template-coding) the Template is changed to a new Template that does not contain any linking (e.g. template-browser), step 1 will not happen, step 3 will not happen either: there will only be a persistent systemd user service that will run removing the links from the previous boot (on the first boot after the Template change it will unlink previously linked files, on the following boots after that there will be nothing to unlink). No dead links, just a user service that effectively does nothing.

One could even make a final adjustment: at the end of the unlinking service script, it checks if the unlinking-service-creator exists. If it does not exist, (Template change), the service will destroy itself by disabling and removing its file, thus ensuring that there are absolutely no side effects after changing the Template.

Help

I see this as a pretty useful feature (I hope it is useful not just for me).

The proposed solution might seem over-engineered, and maybe it is, if a simpler way of solving this problem exists.

If you are interested in this feature, could you help me answer these questions:

  1. Am I missing any safety considerations?
  2. Am I missing anything in addressing those safety considerations?
  3. Is there a simpler way to solve this problem altogether

Thanks,
Monscodex

1 Like

One could add another safety consideration to answer the question: "What if I am refining the salt module and want to change one of:

  • the contents or name of the unlinking-service-creator systemd service
  • the contents or name of the unlinking-service-creator associated bash script
  • the contents or name of the unlinking systemd service
  • the contents or name of the unlinking associated bash script
    ?"

Then the rest of the services will not be aware that one change was made and will maybe enter an invalid state or will produce faulty behavior. One solution would be to append the contents of said values to a string, get the hash of that string and append it to every name of each file.

Example

unlinking-service-creator systemd service is at path w and contains a
unlinking-service-creator associated bash script is at path x and contains b
unlinking systemd service is at path y and contains c
unlinking systemd service is at path z and contains d

h = hash(w, a, x, b, y, c, z, d)

Then h is appended to the names of all of the files:
w = w + h
x = x + h
y = y + h
z = z + h

Then, if a version of the scripts is used with hash h1, and I have modified the contents of the scripts so that it now produces h2

  1. the template deletes all services with different hashes (the unlinking-service-creator-h1 gets deleted)
  2. the unlinking-service-creator-h2 gets added
  3. on the next boot of the appvm that had version 1
    a. the unlinking service will not find any service unlinking-service-creator-h1 so it auto-destroys.
    b. the unlinking service with version 2 gets installed and will run. (it will check for the existence of unlinking-service-creator-h2 instead.

From what I see as the main issues, it is about refreshing data, owned by a template, inside a appvm.

The basic way to solve the pre-configuration is done by the mapping paths /usr/local.orig/ -> /usr/local/ and /etc/skel -> /home/user/ that populate the appvm data with the templates provided data on creation.

The templates “vm config paths” are still available to the vm after its creation. To build a solution upon this construct, we would need a script/service that replaces the templates propagated default with the current appvm state.


Apart from that…

My approach, as well as yours, would lack in the usecase that a common template provides differing configs for different vms.
We wouldn’t be able to provide terminal-cfg-offline + terminal-cfg-online via the same mechanism since both fight for the same path.
Your solution could use a secondary mapping path or config values to solve it, mine is doomed. - Anyway, just a edge case

So your idea could essentially be called “Copy/Replace On Boot” if I understand correctly?

And thank you for the edge case! It’s a good one! Maybe a solution would be to store all configs in the Template’s path, copy/link them all to ~/.config/terminal-cfg-{offline,online} respectively, and let the AppVM link to whatever they would want (Ex: ~/.config/wezterm → ~/.config/terminal-cfg-offline).

I’m not going to solve that edge case :slight_smile: I think it just that, a edge case.

I think it would be more important to find a simple and reliable solution that is minimal in regards of its code and configuration.

Some vms will always need special attention that can’t be handled by a generic script.
Therefore I would like a script that solves at least the repetitive management tasks like deploying a new wezterm config to all qubes.