We all know rc.local. It’s always been the easiest way of adding something to the end of the init procedure. Oh, but not with systemd, the unstoppable bloatware init system, of course.
Something like rc.local could be quite useful though, especially with Qubes OS.
In Qubes OS, your virtual machines are based on templates and only a limited number of directories in the system are owned by the VM itself (unless running standalone VM’s). In other words, those directories (e.g. /home and /usr/local) are non-volatile and data stored within them will survive reboots of the VM, unlike other directories (e.g. /etc) where all data will be reset to the template’s default state when the VM is restarted.
systemd (or at least the Fedora package of it) comes with a service called rc-local.service. It looks like this:
# This unit gets pulled automatically into multi-user.target by
# systemd-rc-local-generator if /etc/rc.d/rc.local is executable.
In one way or another, this service file is not compatible with my templating setup. The directory /etc/rc.d/rc.local is not located in one of the non-volatile directories. This means that I can add stuff into the rc.local file in the template and have those things started in all of my virtual machines which are based on that template, but I won’t be able to add things into it which are specific to just one of those virtual machines (.. unless doing some ugly if statements checking the hostname etc.).
Changing the path to the rc.local file in the service file would solve this issue. But what if the systemd package is updated and the service file is reset? No good.
Lets add a new service file instead and call it /usr/lib/systemd/system/p-rc-local.service.
Description=Qubes rc.local Compatibility
So, the script executed by the service has been set to a file in a non-volatile directory instead. Furthermore, by setting Before=qubes-gui-agent.service, it is made sure that the script is executed before the X Server is started. Quite a useful configuration and I might add some follow-up article regarding this.
Activated/enabling the service is easy (as in always start on boot), just execute:
systemctl enable p-rc-local.service
Have a look at my new post systemd actually works – my thoughts on systemd have slighty changed.
Additionally, now a days my “rc.local” setup looks a bit different. Since many of my virtual machines in Qubes OS have different needs when it comes to running things in the boot process, it’s come down to having some generic services installed in the VM template.
First off – it’s important to know the difference between simple and oneshot systemd services.
- The simple ones are started by systemd and then left alone – the boot process immediately continues. It won’t wait for the simple service to finish – not even if the service has been set to “Before” some other service.
- The oneshot ones are treated differently. When systemd starts one of these, it will wait for it to complete before processing any jobs scheduled post-start of the oneshot service.
I use these two service types as two different hooking mechanisms at two different points of time in the boot process; pre-networking and post-networking/pre-GUI.
Running a proxy VM might require some network/iptables related changes to the machine, hooking in to the boot process even before iptables has been initialized is useful. For example, replacing the ordinary iptables rules file located at /etc/sysconfig/iptables is easy this way. By running it as a oneshot, you can be sure that iptables won’t be loaded until the new rule file has been installed.
Sometimes there’s some action to perform when something has been completed in the boot process. This is usually handled with the “Before” and “After” statements in the unit files, but as it would become a bit of a hassle to add a whole bunch of different unit files like that to the VM template, I like to use this service instead.
Within the executed service script it’s easy to do things like pausing the process until something has been fulfilled, e.g. waiting for some other service to start (polling systemctl status servicename until its ok) – and finally perform whatever is needed for this specific VM. Running this as a simple service makes sure that the whole boot process isn’t stalled by this script waiting for some service that otherwise would’ve been started after this very service has completed, i.e. a deadlock scenario.
This one is great for doing stuff just before the GUI is launched, such as installing some untrusted software blobs (Spotify, Skype, etc.). By running this in a oneshot service, it’s easy to make sure that the installation is finished before the GUI is started. This way it’s possible to run “qvm-run -a vm-name app-installed-at-boot” even if the VM was initially powered off and then get your app-installed-at-boot to pop up.
Perhaps you need to mount some network share? Then this one is great. Just like before it’s easy to add some polling to make sure everything is set up correctly before continuing. Perhaps you’d like to run a ping towards the NFS server and not try to mount the share until you receive the ICMP echo-reply. Running it as a simple service won’t block the boot process, i.e. you’ll still get a GUI running even if the network is down.
The unit files are installed by dropping them into /usr/lib/systemd/system and then executing
systemctl enable qvm-init-*.