In actual fact, the multi-user boot versus single-user boot notion goes back to pretty much the beginning (https://news.ycombinator.com/item?id=10207674), and the name "multi-user" is not exactly difficult to cope with if one has a Unix background. It's not novel or a systemd-ism. It's been a Solaris SMF milestone name for over a decade. It was a concept used in the 4.1 Berkeley Distribution manual for init(8) in 1981.
Sure, but I had to do a bit of reading to understand that
[Install]
WantedBy=multi-user.target
meant: "This service should start in the runlevel called 'multi-user'".It also seems... backwards to put runlevel membership in a service definition file. Runlevels are an administratively controlled thing. Does systemd have two or more places a given service's runlevel membership can be specified, or is the only way to change a service's runlevel in its service definition file?
Yes, I know that the notion of "runlevels" is probably "legacy". However, assume that -on my OpenRC system- I have two particular runlevels, one for regular multi-user operation, and one for operation when I connect to hostile networks. Transitioning to this second runlevel shuts down all services that listen on anything other than localhost. Transitioning back brings those services back up.
Although this situation is a little contrived, this sort of flexibility is valuable.
What systemd actually does at startup, or when starting services individually, is look for symlinks. If I have a foo.service and a bar.service, and a symlink called foo.service.d/bar.service -> ../bar.service, then systemd will start bar.service automatically whenever I ask it to start foo.service. If it also sees a multi-user.wants.d/foo.service -> ../foo.service, then it will start foo (and also bar) whenever it enters the multi-user "runlevel". (This is almost exactly how sysv runlevels work, except that they're named rather than numbered, and they can depend on each other.)
I can make that symlink manually, if I want, or I can call "systemctl enable foo.service" in which case systemd will read the dependency information from the .service files and make the appropriate symlinks.
If I don't like how the distro or packager has set up the service file, I can override it. The service files live in /etc/systemd/system by default, but systemd also looks in /var/systemd/system for overrides. This is the real reason why systemd uses key=value stores for units; merging them is trivial while merging shell scripts is decidedly not.
Putting the overrides in a separate directory is a pretty nice decision; it's always obvious what changes have been made, and upgrades never overwrite them. I can even put those overrides into version control and track changes to them over time.
Is this true of the identically-named keys in the Unit section?
> If I don't like how the distro or packager has set up the service file, I can override it. ... This is the real reason why systemd uses key=value stores for units; merging them is trivial while merging shell scripts is decidedly not.
You should look at how OpenRC handles service dependency specification. It's at least as flexible as the system you describe in systemd. :)
> Putting the overrides in a separate directory is a pretty nice decision...
Yep. At dependency graph generation time, OpenRC examines its master config file at /etc/rc.conf as well as config files in /etc/conf.d/ for -among other things- additions to the need, use, before, &etc declarations in the depends section of a given service's init script. To augment -say- the "use" list in the openvpn service, either add the line
rc_use="my_custom_service"
to /etc/conf.d/openvpn , or add the line openvpn_rc_use="my_custom_service"
to the OpenRC master config file at /etc/rc.conf . If I -say- wanted to override OpenVPN's default dependency on the "net" virtual service, I would add rc_need="!net"
to /etc/conf.d/openvpn (or perform a similar transformation as before and store in /etc/rc.conf).> I can make that symlink manually, if I want, or I can call...
To add the openvpn service to the "custom_init" and "default" runlevels, I would simply do
rc-update add openvpn custom_init default
If I omit the runlevel, the service is added to the current runlevel. I could screw around with symlinks if I cared to, but why would I? :)The OpenRC configuration processing system is substantially more powerful than I describe, handles the processing of OpenRC-standard config files [0] automatically, and lets you shift the complexity of a configurable init script that should be in a config file into a config file.
Because my overrides and augmentations to the distro-supplied init scripts are in text files stored in standard places, I -too- can easily put them in version control and track their changes over time.
[0] Which -shockingly enough- look exactly like what any Linux programmer would expect a non-XML non-JSON text config file to look like. ;)