Systemd: Run it last ...

You remember the good ol' days when running a command last was as easy as

echo "last_command" >> /etc/rc.local

Sure you do, and I bet you also know that now that systemd has become the default init on pretty much all major distributions, it doesn't work anymore.
Wait, /etc/rc.local still works and a lot of people will jump on the occasion to tell you, but NO, it doesn't work to run your command last. It's even written there in the file

#!/bin/bash
# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES
#
# It is highly advisable to create own systemd services or udev rules
# to run scripts during boot instead of using this file.
#
# In contrast to previous versions due to parallel execution during boot
# this script will NOT be run after all other services.
#
# Please note that you must run 'chmod +x /etc/rc.d/rc.local' to ensure
# that this script will be executed during boot.

touch /var/lock/subsys/local

Yes that means that putting command there will have them run in parallel with the other services and you have no control over it.

Did it need improvement? Well maybe, I particularly like how openrc has handled the local service. Let's look at the README in /etc/local.d

This directory should contain programs or scripts which are to be run
when the local service is started or stopped.

If a file in this directory is executable and it has a .start extension,
it will be run when the local service is started. If a file is
executable and it has a .stop extension, it will be run when the local
service is stopped.

All files are processed in lexical order.

Keep in mind that files in this directory are processed sequencially,
and the local service is not considered started or stopped until
everything is processed, so if you have a process which takes a long
time to run, it can delay your boot or shutdown processing.

Seems like a smart way of enhancing the feature to me.

But let's get back to our concern, how does one run a command last on systemd?
When you reach to people which are supposedly knowledgeable about systemd, you often end up with this kind of discussion:

you: Hello, I'm wondering what would be the best way to paint my car red?
them: Why don't you first start telling us why you think you need to paint your car red
you: ??? I want it red that's all ???
them: Well usually people think they _want_ their car red, but that's wrong, they just _need_ it black
you: Well I don't _need_ it black I _want_ it red
them: Yep, you _want_ it black

Yes, true story, people will try to make you think that you don't need/want to run anything last. Well aren't we using Linux because we like the idea of being able to do what we want with our systems? Seems like mentality is changing...

Come on there must be a way?! Yes there is, and this is how you do:

  • Create a new custom target
  • Make this target Requires multi-user.target
  • Create a unit file for your command with After multi-user.target
  • Put the unit file in custom.target.wants
  • Make this new custom target default

Sounds easy right? Here's a bit more details.

Create a new custom target

/etc/systemd/system/custom.target

[Unit]
Description=Custom Target
Requires=multi-user.target
After=multi-user.target
AllowIsolate=yes

Here you have a new target that should run after multi-user.target that can be used in a similar way as a runlevel thanks to AllowIsolate

Create a unit file for your command

[Unit]
Description=My last command
After=multi-user.target

[Service]
Type=simple
ExecStart=/sbin/last_command

[Install]
WantedBy=custom.target

After is important here else it'll be run in parallel with other services of multi-user.target. You wanna put this service file in /etc/systemd/system/custom.target.wants directory.

Make your new target default

systemctl list-units --type target --all

Should show you the complete list of available targets, with your custom.target being inactive

systemctl isolate custom.target

Will switch your current target to your custom.target. Good time to see if it's working and debug.

ln -sf /etc/systemd/system/custom.target /etc/systemd/system/default.target

Will switch your custom target to the default.

You can now reboot and enjoy your "last_command" being run last, but note that services that take time to start, like a Java app, may very well still be starting when "last_command" will be executed.
If this is an issue you'll have to play with ExecStartPre=/ExecStartPost=

When I have to do this to simply run a command last in my boot process, I clearly miss the good ol' days, and luckily there's still a bunch a excellent distributions that keep the KISS principle as a priority.

As usual, feel free to contact me!