Systemd – Correct Substitute for rc.local in Systemd

rc.localsystemd

I cannot find the correct way to execute some local scripts (or very local commands) at systemd, I already know I must not create a service (in systemd a unit) for this kinds of scripts (or I must?)….

The workaround that I found is to create rc.local and give to it execution permissions.

printf '#!/bin/bash \n\nexit 0' >/etc/rc.local 
chmod +x /etc/rc.local

For example, if I get a legacy server with a simple rc.local configured by you, I will know what did you do and how much it gonna hurt to upgrade or install something new on the distro, as rc.local was respected by external packages, but in the other hand if I install a server and create a systemd unit or two or three (or even sysvinit services), just for doing a simple task, this can sometimes make your life harder, and much more than this my units names can someday conflicts with the names of the new services created by the distribution development, and maybe installed on a upgrade, causing trouble for my scripts !

I see another question asking about where is rc.local and the answer was to create it and give execution permissions, I think my question is really not a duplicate, because I do not want to know where it is – believe me, I just want to accept that it is deprecated, but i cannot find the correct way for doing this kind of things, should I really create a unit just for some simple like that?

Best Answer

As pointed out elsewhere, it becomes moderately unclean to use rc-local.service under systemd.

  1. It is theoretically possible that your distribution will not enable it. (I think this is not common, e.g. because disabling the same build option also removes poweroff / reboot commands that a lot of people use).
  2. The semantics are not entirely clear. Systemd defines rc-local.service one way, but Debian provides a drop-in file which alters at least one important setting.

rc-local.service can often work well. If you're worried about the above, all you need to do is make your own copy of it! Here's the magic:

# /etc/systemd/system/my-startup.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/libexec/my-startup-script

[Install]
WantedBy=multi-user.target

I don't think you need to understand every single detail[*], but there are two things you need to know here.

  1. You need to enable this with systemctl enable my-startup.service.

  2. If your script has a dependency on any other service, including network-online.target, you must declare it. E.g. add a [Unit] section, with the lines Wants=network-online.target and After=network-online.target.

    You don't need to worry about dependencies on "early boot" services - specifically, services that are already ordered before basic.target. Services like my-startup.service are automatically ordered after basic.target, unless they set DefaultDependencies=no.

    If you're not sure whether one of your dependencies is an "early boot" service, one approach is to list the services that are ordered before basic.target, by running systemctl list-dependencies --after basic.target. (Note that's --after, not --before).

There are some considerations that I think also applied to pre-systemd rc.local:

  1. You need to make sure your commands are not conflicting with another program that tries to control the same thing.
  2. It is best not to start long-running programs aka daemons from rc.local.

[*] I used Type=oneshot + RemainAfterExit=yes because it makes more sense for most one-shot scripts. It formalizes that you will run a series of commands, that my-startup will be shown as "active" once they have completed, and that you will not start a daemon.