Speeding up OpenRC Startup
This guide contains some tips and tricks to get faster boots with OpenRC.
TL;DR
- Move non-critical services to a separate custom runlevel;
- Write a boot script that switches to that runlevel in the background, allowing you to log in while some services finish starting up;
- Set rc_parallel to "YES" in /etc/rc.conf.
The Problem
By default OpenRC will start all services in the runlevel before it presents you
a login prompt (specifically referring to the getty, I don't use a display
manager so I cannot comment on those). This takes up a lot of boot time for no
good reason. Do you really need your network related services to be running
before you log into your desktop? Couldn't they just start in the background,
and be done by the time your bloated web browser finally opens?
I couldn't find any properly documented solution for this problem, so I had to come up with a little hack. The best I could find was this Reddit thread, which describes the exact problem but does not provide a direct solution. It does however mention the use of custom runlevels, which I did end up using.
The Solution
Here's an overview of the hacky solution I came up with:
- First of all, we create a custom runlevel;
- Secondly, we move all services from the
defaultrunlevel that we wish to start in the background into it; - Then we stack the
defaultrunlevel onto it; - Finally we write a little boot script that switches to our custom runlevel asynchronously, starting all required services in the background.
Let's break it down.
Creating a Custom Runlevel
I called mine async. Name it whatever you want. Run the following as root:
mkdir /etc/runlevels/async
As easy as that.
Moving Stuff Over
Now we will move most of the services from the default runlevel into async.
In my case it was safe to move all except for the gettys and local (you may
not have gettys there if booting with sysvinit). The easiest approach here
is probably to directly move the symlinks from /etc/runlevels/default/ into
/etc/runlevels/async/, although you can also use utilities such as rc-update
as you normally would. Here's an example (run as root, add or remove services as
needed):
cd /etc/runlevels/default
mv net.* sshd chronyd distccd tor libvirtd ../async
Stacking Runlevels
Runlevel stacking is what they call adding a runlevel to another runlevel, i.e.
inheriting services from another runlevel. We need this because otherwise the
services from the default runlevel would be stopped upon switching to the
async runlevel (unless they were also added to it). Run the following as root:
rc-update add -s default async
Starting It Asynchronously on Boot
Note: If you use OpenRC to start the gettys, e.g. you use openrc-init,
then it would suffice to boot into the async runlevel by appending
softlevel=async to the kernel cmdline. But the getty will get buried by the
console logs of the services in async, so you might prefer these next methods
that also don't involve messing with the kernel parameters.
I find that the simplest way to achieve this is through a custom script in
/etc/local.d/, but for some more complex setups it might be better to write an
init script instead (i.e. in /etc/init.d/) and add it to the default
runlevel. The local.d approach will work as long as you have the local service
enabled in the default runlevel, which at least in Gentoo should be there if
you haven't removed it.
Simply create the file /etc/local.d/async.start (or anything in that directory
ending in .start) with the following contents:
#!/bin/sh
/sbin/openrc async &
And make it executable: (as root)
chmod +x /etc/local.d/async.start
And you're all set! All the script is doing is switching to the async
runlevel, using the '&' character to background the process.
Bonus: Parallel Service Startup
You might already know this, but here is a general tip for faster OpenRC boots:
Enable parallel service startup by editing /etc/rc.conf and ensuring that you
have an uncommented line with rc_parallel="YES". This will considerably speed
up the boot process but won't fix the main issue I've described. However it is
safe to use together with the solution that I've demonstrated here.
Conclusion
And just as I was about to post this, I checked again for existing solutions, and guess what I found: NEARLY THE EXACT SAME THING POSTED BY SOMEONE ELSE 3 YEARS AGO! With the same runlevel name and all. I swear on my life it's the first time I'm looking at it.
Seriously though, go check it out if you're using sysvinit, it seems cleaner
than my approach. It will not work for openrc-init however, which means my
article is still kinda relevant and I'm slightly less mad.