systemd service handling/auto-enabling
How pmOS manages service autostart config in pmaports has been a really important topic for me, this issue is about the design of that. !5377 (merged) is the latest attempt to implement this design. I've written it up below for posting to the wiki after this is merged:
Summary
This describes the design for configuring and maintaining systemd service autostart preferences in pmaports. It's intended to be used by pmaports package maintainers. It may also be helpful for system admins who would like to understand how pmOS performs autostart configuration for systemd user and system services.
Goals
Users should have ultimate control in how their system is configured and pmOS maintainers need a reliable, easy, and consistent way to provide default configuration for users on all device and UI combinations that we support.
With systemd, maintainers and users have a lot more options for specifying service autostart preferences. We found that some of these options can create service autostart config conflicts between users and pmOS package maintainers. For example, users might run systemctl disable foo
and a package update might systemctl enable foo
and override the user's preferences. This has happened in the past with our openrc config. The following requirements were used to find a design that prevents (or greatly reduces) these types of conflicts:
- Allow pmOS components (such as UI and device packages) to specify default autostart preferences.
- Allow users to override preferences using well known methods (e.g.
systemctl disable
...) - Packages should not override user config
- Use something similar to other distros that also solve this problem
- Allow package managers to conditionally enable/disable autostarted services on package upgrade, useful for replacing one service with another.
Design
Systemd service autostart config is handled using systemd's preset feature. Some major distros, like Fedora, use systemd presets. A lot of the following design was inspired by how other distros use this. Presets, when used in the following way, allow us to meet all of the previously stated goals.
The general overview is:
- pmOS packages install systemd preset config for services
- packages that install systemd unit files run an install script macro to apply preset config on installation
- Those same packages also run a different macro on uninstall to clean up symlinks
Distro system and user service presets are installed by the postmarketos-base-systemd
package. Other packages may provide presets too, but care must be taken to insure that service packages using the systemd install script macros must be installed after any package providing a preset config for that service. If this cannot be guaranteed, then the expected preset may not be applied.
Service packages that install systemd unit files should use post-install
and pre-deinstall
install script macros when those unit files include an [Install]
section. The macros are provided in the file /usr/share/systemd/systemd-apk-macros.sh
ll], this file is installed by the systemd package. The relevant install scripts are expected to source this file. There are two functions in this file, one for each type of install script:
systemd_service_post_install
and systemd_service_pre_deinstall.
Both scripts accept either user or system as the first param (for type of service), and then a list of services.
Example using ficticious foo
service, where foo.service
and foo@.service
unit files are installable:
$ cat foo-systemd.post-install
#!/bin/sh
. /usr/lib/systemd/systemd-apk-macros.sh
systemd_service_post_install system foo foo@bar
$ cat foo-systemd.pre-deinstall
#!/bin/sh
. /usr/lib/systemd/systemd-apk-macros.sh
systemd_service_pre_deinstall system foo foo@bar
Old, outdated proposals and context
proposal (outdated)
This is being proposed by @jane400, and she originally had this written up in a separate document. I moved it here to help make review/discussion easier.
systemd presets on postmarketOS
presets allow us to define policy as a file, which can be looked up. In comparison to our current approach with openrc/systemd which has those policies encoded in our post-{install,upgrade} hooks.
initial install
- we install the preset files (we have to ensure that those preset files are installed before a -service package)
- every -service subpackage has a post-install hook which runs
systemctl preset
on the specific service - depending on the preset policy, this service is now enabled or disabled.
this replaces the .post-install in the meta packages which run rc-update add $service default
To ensure step 1 succeds, we need to place the preset files before everything, so the pmos-base-systemd package is the perfect fit for this. Placing presets in base-ui or ui-* brings the danger of an important service being installed before the preset and getting hence not enabled. We can workaround this by using a post-install hook in this preset package and "doing it the old way".Example usage in base-ui-iwd-systemd:
#!/bin/sh
# .post-install
systemctl --no-reload disable --no-warn wpa_supplicant.service || true
systemctl enable iwd.service || true
changing the policy/upgrading
if we're changing something in the presets, we want to run that change on the current system. this is how we accomplish this:
- change the preset, e.g. adding
enable foo.service
- we create/append to the post-upgrade hook a run to
systemctl preset foo.service
:
#!/bin/sh
# The old version of the package is passed in as the second argument
OLD_VER="$2"
NEW_VER="$1"
old_ver_less() {
[ "$OLD_VER" != "$1" -a "$(echo -e "$1\n$OLD_VER" | sort -V | head -n1)" = "$OLD_VER" ]
}
have_systemctl() {
command -v systemctl > /dev/null
}
is_systemd_running() {
[ -d "/run/systemd/running" ]
}
if old_ver_less "31-r1" && have_systemctl; then
systemctl preset foo.service > /dev/null || true
fi
The good thing about this approach is, that we always apply the state that is currently the preset and don't override user-preferences/presets.
This migration script is in pmos-base-systemd, as that service might already be installed. If it's getting installed later it will just use the usual initial installation
path.
benefit
Now: just installing gdm via a depends postmarketos-ui-gnome should pull in gdm-service, which gets enabled. No additional hook. If the user were to install multiple display-managers or UIs the first one that was enabled wins, as only can have the display-manager.service aliasIn the future: we should be able to just nuke /etc/systemd and /etc/machine-id and presets+machine-id would get auto-generated by systemd.
for comparison: our current approach
Best case we're doing this:
# postmarketos-base.post-upgrade
if old_ver_less "31-r1"; then
echo "## postmarketos-base migrating to logbookd ##"
echo "## The initial upgrade didn't disable syslog, we're doing that now ##"
echo "## If you want to keep using syslog, undo the following changes ##"
set -x
rc-update add logbookd boot
rc-update del syslog boot
set +x
fi
Most times we're actually running these commands on every upgrade.
# also postmarketos-base.post-upgrade
# To turn zram-init into a no-op, set: deviceinfo_zram_swap_pct="0"
rc-update -q add zram-init default
# We once enabled tmpfs in default, when it needs bootmisc: pma#2473
rc-update -qq del tmpfs default || true
# To disable mounting /tmp as tmpfs deviceinfo_tmp_as_tmpfs_size="0"
rc-update -q add tmpfs boot
exit 0
This translates 1:1 to systemd, but we're currently not doing that. We have an hacky workaround/stub for rc-update
so those rc-update
calls get translated to systemctl {enable,disable}
calls...
As we want to get rid of those calls/this stub, we need to use systemctl directly, aka almost duplicating every single rc-update
reference we have. As some of our meta packages places files under /etc/conf.d
to configure openrc-service it would make sense to only install them on "openrc-based" systems. Likewise for systemd, so we would end up with a bunch of -openrc and -systemd subpackages.
The same would apply now for .post-install too, if we were to use systemctl
directly.
alternatives
checking for init-system binaries
we don't have post-{install,upgrade} scripts for $pkgname-openrc, $pkgname-systemd, we rather put those hooks into $pkgname and check which command exists:
# example for a .post-install
have_systemctl() {
command -v systemctl > /dev/null
}
have_rc_update() {
command -v rc-update > /dev/null
}
if have_systemctl; then
systemctl enable phosh.service
fi
if have_rc_update; then
rc-update add phosh default
fi
exit 0
This brings the benefit of having everything in one place and getting rid of a bunch of -{systemd,openrc} subpackages, but we're still essentially duplicating everything, but just in one file. There would also be some complications, as we need phosh-service
phosh-openrc
to be depended upon in the $pkgname, as otherwise these commands will fail.
TLDR
We're not going to get rid of -systemd subpackages in pmaports, but we wouldn't need them on post-install with the preset approach, as every package itself takes care that it's enabled (as specified in the preset policy). This results in us mostly splitting openrc specific stuff out to subpackages and having almost no systemd post-install for that use case.
related links
https://src.fedoraproject.org/rpms/fedora-release/blob/rawhide/f/90-default.preset
Background / Historical context
We need to figure out a good way to handle system and user services in pmOS, for new and existing installations with systemd.Specifically, we need:
- Enable service after package install
- e.g. UI package installs a service dep and then configures system to run it on boot
- Conditionally enable or disable service after package upgrade
- e.g. package depends on a new service, see !4549 (merged)
- requires package version comparison, Arch Linux uses vercmp. Maybe that's helpful?
- Do not overwrite user config
- e.g. we enable a service by default, user then disables it, we don't want to re-enable it on upgrade
Currently on openrc, we use $pkgname.post-{install,upgrade}
scripts in packaging to do most of this stuff, like run rc-update add
. If we extend this same pattern to systemd we'll need to create new subpackages for every init system that runs its equivalent of 'enable/disable service'.
Currently for systemd we are splitting out new subpackages[1] or relying on a wrapper for rc-update
[2]. Making new subpackages isn't great because it results in a lot more files in pmaports (I find this annoying to manage), and it can lead to a lot of duplication. This works, but I don't like having to create/maintain a bunch of init-specific subpackages in pmaports to implement this. Wrapping rc-update seems weird, and it gives a lot of false errors (e.g. when a post-install calls it with -qq, a common pattern in pmaports).
systemd presets[3] can be used to configure services to start automatically for new installations, but presets cannot be used easily for configuring services on existing installations since it may conflict with user configuration.
- example of using init-related subpackages for toggling services
- drop systemd rc-update wrapper script
- use systemd presets and split into init system specific subpackages
Also see: