Tracking issue for upstream work: https://gitlab.alpinelinux.org/alpine/abuild/-/merge_requests/272 Might or might not be that it needs to be reworked, but that is pending on ncopa's feedback. Waiting for that to move forward. This would allow to drop systemd/abuild: new aport from the systemd branch
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information
Child items 0
Show closed items
No child items are currently assigned. Use child items to break down this issue into smaller parts.
Linked items 0
Link issues together to show that they're related.
Learn more.
Alpine would be fine with us adding a -systemd split function (see Natanael's comment)
However, as I understand it we realized that not only we need to add systemd .service files, but also run a hook that runs systemctl preset $service after installation so it correctly gets enabled/disabled based on distro + user preferences.
That's why we did not move forward with the upstream MR yet. For now we have service files packaged downstream in postmarketOS in systemd-services and abuild forked with the patch for splitting systemd service files.
No, this doesn't work because we don't actually get any info in the trigger about files being added/removed, so we cannot detect when we need to run preset. We need to detect this to avoid always running preset and overwriting any user config
@caleb, @pabloyoyoista: I wonder if we can build it so that we add an apk hook in postmarketos-base-systemd, which runs systemctl preset $service after any systemd related package was installed, and then move forward with getting the split services patch into abuild upstream?
I think i proposed that the other day in the chat channel and I think that the problem was that ideally systemctl preset should only by ran after a package installation and not package upgrades, etc.
I just verified on a systemd machine that always running preset will override user's choice of disabling/enabling services.
Indeed it does.
If you enforce presets during package installation/upgrade, then the system administrator needs to configure enabled/disable services via presets too. They can no longer configure services via systemd enable or systemd disable.
these systemd-post-install / systemd-pre-deinstall files are not provided by abuild itself, but would be provided by postmarketos-base-systemd (or in theory other users of the -systemd subpackages from alpine)
the scripts would look something like this:
Example with systemd-post-install:
#!/bin/sh -e. /usr/lib/systemd/systemd-apk-macros.shpkgname="$1"service_files="$(apk info -L"$pkgname")"for service_file in$service_files;do if is_user_service "$service_file";thensystemd_service_post_install user "$(basename"$service_file")"elif is_system_service "$service_file";thensystemd_service_post_install system "$(basename"$service_file")"elseecho"systemd-post-install: can't handle $service_file (url-to-learn-more)"fidone
So we would only hardcode the logic whether a service is a user service or system service (or needs to be ignored...?) in a script that is shipped by postmarketOS, and we can upgrade it independently.
I wonder if we could even detect programmatically whether a service is a user service or system service, based on the sections it has etc. Then we wouldn't even need to include lists of services.
I wonder if we could even detect programmatically whether a service is a user service or system service, based on the sections it has etc. Then we wouldn't even need to include lists of services.
AFAIK there's no reliable way to detect this in the unit file(s), but you could look at where the build system/apkbuild installs the unit file(s), e.g. system services are under /usr/lib/systemd/system and user services under /usr/lib/systemd/user
The other thing to consider is that not all unit files are meant to be "started", for instance services of the dbus type don't have an [Install] section. I think systemctl preset complains if you run preset on a service/unit that it can't enable/disable, I don't recall if it's fatal or if we could just ignore it so we would run systemctl preset on everything and not try to parse unit files to detect it... maaybe parsing is more desirable, assuming there's a reliable way to tell if preset can do anything with a unit file?
AFAIK there's no reliable way to detect this in the unit file(s), but you could look at where the build system/apkbuild installs the unit file(s), e.g. system services are under /usr/lib/systemd/system and user services under /usr/lib/systemd/user
perfect! (we have the path at this point, can just look at that then)
The other thing to consider is that not all unit files are meant to be "started", for instance services of the dbus type don't have an [Install] section. I think systemctl preset complains if you run preset on a service/unit that it can't enable/disable, I don't recall if it's fatal or if we could just ignore it so we would run systemctl preset on everything and not try to parse unit files to detect it... maaybe parsing is more desirable, assuming there's a reliable way to tell if preset can do anything with a unit file?
Looks like it does not complain and exit code is 0:
# systemctl status dbus.service● dbus.service - D-Bus System Message Bus Loaded: loaded (/usr/lib/systemd/system/dbus.service; static)
# sudo systemctl preset dbus.service && echo okok
EDIT: so I mean, I'd just always run systemctl preset on all services then, that is the easiest
Thanks for taking a look, the feedback and for proposing an alternative (see my comments below)! In general I don't care which approach we use, as long as it works and Alpine is happy with it. Great that we have multiple now!
templates (also we might want to preset some specific instance of a template service)
We could deal with that in systemd-post-install / systemd-pre-deinstall scripts too, e.g. ignore files ending in @.service like lxc@.service by default (like we seem to do currently in the systemd-services package). And if needed, if we see a specific servicename@.service file, then we run systemd_service_post_install on servicename@first.service, servicename@second.service, etc.
We have to define somewhere which instances of templates we run systemctl preset on (in your approach below it would be in the APKBUILD). By placing it withing scripts we install from postmarketos-base-systemd, we have more control over it and can adjust it more easily without modifying Alpine packages or abuild.
And as I understand, this is rather the exception, for most -systemd subpackages we don't need to run preset on templates? I'm not sure though, correct me if that is wrong.
drop-ins and other files which aren't whole unit files
How would that look like, can't we just filter by *.service and *.socket and ignore the rest?
running apk info -L is far from instant, I don't think this would scale very well for tens or hundreds of packages
Right, on my Laptop with lots of packages installed it takes ~0.5s which is not great. How about we generate the list of files inside the package in abuild at the time when we build the package, put it into the automatically generated post-install and pre-deinstall scripts of the systemd subpackage as variable and pass it to the systemd-post-install / systemd-pre-deinstall scripts as additional arguments so they don't need to look it up? Then we can completely avoid this delay.
So I guess this is something that can work, and would save us having to duplicate information inside the APKBUILD. I feel much more confident knowing there is an implementation we can use as inspiration.
It seems like template units would still need to be handled explicitly, so having the logic like I described below still seems somewhat reasonable(?) for this case, but at least it would only be necessary in certain cases.
On further inspection, the debian helper is actually wrong regarding template units since they may contain a DefaultInstance= property, for example in getty@.service there is DefaultInstance=tty1, resulting in:
@caleb according to man systemd.preset, you can put instance names in the preset to control what actually gets enabled when systemctl preset thing@.service is run. This apparently can override DefaultInstance too. So, I think running preset on everything is OK and IIF we need to control what the instance is we can add it to postmarketos-base-systemd/*.preset
An alternative proposal (new comment to avoid complicating the thread)
I really think the most sensible option here is to put the work in to teach abuild to understand systemctl preset functionality. Something like this:
put systemd-apk-macros.sh in abuild.git in a new plugins/ subdir and package it as abuild-plugin-systemd, the systemd package would then depend on this.
Install it to /usr/lib/abuild/plugins/systemd/systemd-apk-macros.sh
On the APKBUILD side, the ideal API is going to be something like:
For this to work, we need to translate these variables into a snippet of script that runs during post-install and pre-deinstall. A naive approach would be to add those install files if not already there, otherwise append to them. However this won't work in cases where exec or exit 0 are used at the end. But something like this should work:
#!/bin/sh -e### auto-generated systemd preset configuration start ###trap"abuild_plugin_systemd_configure_presets" EXIT_abuild_plugin_systemd_configure_presets_did_run=falseabuild_plugin_systemd_configure_presets(){[[$_abuild_plugin_systemd_configure_presets_did_run]]&&return. /usr/lib/systemd/systemd-apk-macros.shsystemd_preset_system="foo.timer foo.service"systemd_preset_user="foo-user-helper.service"for service_file in$systemd_preset_system;dosystemd_service_post_install system "$service_file"done for service_file in$systemd_preset_user;dosystemd_service_post_install user "$service_file"done_abuild_plugin_systemd_configure_presets_did_run=true}### auto-generated systemd preset configuration end ###... the rest of the script as normal
this way, the preset handling stuff always runs at the end, but if necessary (for some reason) you could call abuild_plugin_systemd_configure_presets directly from the top of the usual post-install to do it first.
The only limitation here is that we have to make assumptions about the shebang (e.g. that it's /bin/sh not some other interpreter and has/doesn't have specific flags), I checked aports and there's only one use of a shebang in a post-install which isn't #!/bin/sh and that's a single use of execline, so I think this is an acceptable limitation.
Ok so the real issue is how to package this all inside abuild. For this I see two possibilities:
we're able to get away with adding all this logic straight-up
We actually add a plugin API
Honestly, I think (2) would not be too bad, particularly if we (At least initially) have the API be strictly private with all users being scripts that are inside abuild.git. Obviously we would need to collaborate with the abuild maintainers on this, but I think it's a sensible approach.
Possibly in the short term we can go with (1) and migrate to (2) as a mid/long term goal if Alpine folks agree.
Thank you very much for making this proposal and thinking through how we could solve it!
Maybe I'm missing some things, but I don't think this proposal could get accepted upstream as-is as it requires too many changes to APKBUILDs (or alternatively we would only use it in postmarketOS, but then it is way less useful). In general, I think the less stuff we put into abuild and especially each individual APKBUILD using this in Alpine, the more likely it is that we can get this accepted upstream.
On the APKBUILD side, the ideal API is going to be something like:
This reads like you propose adding this to the mainpackage() function, and (automatically) adding post-install and pre-deinstall scripts to each main Alpine package that has systemd services. I would guess that upstream finds such a change to regular packages just for adding systemd support unacceptable, can't we have all this in -systemd subpackages instead?
The -systemd package could automatically depend on the main package, if your concern is the order when packages are installed.
Adding systemd_preset_system and systemd_preset_user variables in each APKBUILD to support properly packaging the systemd services is way more than is necessary compared to openrc subpackages.
We actually add a plugin API
Note that this is only really useful to have in Alpine, if abuild is building the systemd subpackages automatically / with very small adjustments (my proposal above would be: it nags about /usr/lib/systemd service files not being in -systemd subpackages, developers only need to add subpackages="... $pkgname-systemd", exactly the same as for -openrc and they are done).
What does not work is, if abuild only creates -systemd subpackages properly if the abuild-systemd plugin is installed. Because then it won't be installed at the time when Alpine builds its packages, and we won't automatically get all the service files packaged.
This reads like you propose adding this to the mainpackage() function
No, brainfart on my part. these variables could actually be top-level variables I think, or per-package functions (like install= is).
Adding systemd_preset_system and systemd_preset_user variables in each APKBUILD to support properly packaging the systemd services is way more than is necessary compared to openrc subpackages.
right, we need additional metadata to support presets correctly (whether that's explicit or somehow autodetected).
What does not work is, if abuild only creates -systemd subpackages properly if the abuild-systemd plugin is installed. Because then it won't be installed at the time when Alpine builds its packages
imho that's quite a leap in logic. there are many ways this could be handled elegantly (the most obvious to me would be abuild adding abuild-plugin-blah to makedepends for subpackages="foo-blah" or something like this), but the details aren't super important there.
Perhaps this was discussed elsewhere but: why are you trying to automatically enable services upon installation? It is usually considered bad practice for distributions to do this (notably: Alpine and ArchLinux don't auto-enable any services).
On my current setup, I have a couple of dozen -openrc services installed, but I don't enable them because they're not services that I need. Some of them are dependencies of wanted package, others are wanted packages that I simply don't run as a system service.
Some examples of services files install which I have kept disabled: containerd, docker, greetd, iptables, librespot, qemu, rsync, syncthing, xandikos, dnsmasq, agetty, wpa_supplicant.
Two or three of the services on that list would likely have broken my network setup if they had been automatically enabled, and another would likely have messed up my tty0.
I'd suggest triggering presets on firstboot or a similar mechanism, but not after package installations.
This isn't about auto-enabling services in Alpine, it's about setting up a mechanism that allows downstream distros (like pmOS) and system admins a way to auto-enable (or force disable!) services on install. Ideally they would be able to provide a .preset file listing their service preferences, and those would get applied automatically when services are installed.
We (pmOS) absolutely want to have the ability to auto-enable things by default, we currently do this in a very hack-y way with openrc and install scripts.
In many cases, new features are enabled in the distro/device by adding services after the initial installation, hence the reason why we need this mechanism after first boot. We (pmOS) do this for openrc stuff.
For each device ship a target file. E.g.: oneplus-enchilada.target.
For each service required on a specific device, add a Wants= directive to that target.
Enable the device-specific target when a device image is created.
When a new service is required for proper support on a given device, you'll add a Wants= entry to the device's target.
This way, you don't explicitly enable services, you merely declare them as required for a given device, and that device's image is configured to start services required for it.
Hmm that's an interesting approach I haven't considered yet... If you list a service as "Wants=" in a .target, you still have to effectively systemctl enable <service> to create symlinks right? IIRC having to still run systemctl preset/enable bootmac to actually get that to run even though it was added in the bluetooth.target here: !6050 (diffs) In that case, we still have to run preset to enable the service a device wants?
I'd also be worried that this moves the service pref config back into each and every device and UI pkg, like we do for openrc with install scripts all over pmaports that run rc-update add and stuff. In your example we wouldn't use install scripts, but hopefully the comparison makes sense. I was hoping to get away from this because it's very error-prone -- we often miss devices or UIs when changing service prefs, and toggling a service pref could easily mean modifying dozens (or more) packages instead of a single line in a single preset file.
Thanks for the additional ideas btw, I'm not trying to be dismissive, it would be nice to solve this more elegantly since the abuild changes needed to support using presets the way that Debian and Fedora do aren't exactly trivial
If oneplus-enchilada.target includes Wants=some-sensor-specific-daemon.service, then you don't need to enable some-sensor-specific-daemon.service; systemd will resolve that it is wanted at runtime and start it. Usually, this will be during the next reboot.
You only need to make sure that oneplus-enchilada.target is enabled, but you can do this when you build the image.
I'd also be worried that this moves the service pref config back into each and every device and UI pkg
You can also have UI-specific targets which have Wants for those UIs.
The complexity has to live somewhere.
Presets
You can also use per-device and per-ui preset files, and have a default fallback value of ignore, so anything not explicitly enabled/disabled by these presets is left untouched.
You can then use trigger= to just run systemctl preset to apply all presets. You don't need to know which one was just installed/updated.
If a user want to override the preset policy, they need to either create their own preset, or mask the service.
Keep in mind that presets are a mechanism to enforce policy, not to trigger one-shot actions.
Debian
The way Debian does things is terrible: each time any service is installed (either intentionally or as a [transitive] dependency), it is automatically enabled. Please don't try to imitate that.
Fedora
So far, whether a service is enabled or disabled after package installation is encoded in the %post scripts of the RPM, and decided globally for the entire distribution, ignoring the particular needs of spins.
If oneplus-enchilada.target includes Wants=some-sensor-specific-daemon.service, then you don't need to enable some-sensor-specific-daemon.service; systemd will resolve that it is wanted at runtime and start it. Usually, this will be during the next reboot.
This didn't seem to actually work for auto-starting, as I mentioned in my recent example w/ bootmac, we had to actually 'enable' the bootmac.service for it to end up in /etc/systemd/system/bluetooth.target.wants... which ultimately means running preset
You can also have UI-specific targets which have Wants for those UIs.
The complexity has to live somewhere.
IMHO having the complexity in abuild to apply presets if they exist for a service when that service is installed keeps the complexity entirely out of packaging and pkg maintainers don't have to think about it aside from adding subpackages=$pkgname-systemd (exactly like for openrc). A major goal here is to reduce the boilerplate/load for package maintainers to support our use case.
The way Debian does things is terrible: each time any service is installed (either intentionally or as a [transitive] dependency), it is automatically enabled. Please don't try to imitate that.
FTR we aren't going to enable everything by default "the debian way", I was merely pointing out that they also use systemd.presets (they just have one that enable *, which we don't want to do). Presets seem to be The Way for distros, sys admins, and such to specify service startup preferences, and having a thing apply any presets for a unit file when it is installed is standard for some major distros. We're not making this up
Ya we deviate from having service preset preferences in service pkgs, what we are doing here is more like spins, but not ignoring our needs... Hence the install scripts for services running 'preset' just in case there are presets specified (we will give presets, Alpine should not)
Vaguely related: keep in mind that you can automatically start services when specific hardware is detected via udev rules.
usbmuxd does this; it automatically starts when you plug in a compatible device.
Ya I've played with this some previously, it's helpful in some very specific situations where udev is triggered but many services couldn't use it (like networkmanager, most user services, UI-related stuff etc)
This didn't seem to actually work for auto-starting, as I mentioned in my recent example w/ bootmac, we had to actually 'enable' the bootmac.service for it to end up in /etc/systemd/system/bluetooth.target.wants... which ultimately means running preset
Do you have a link with more background on this?
If you explicitly list on unit as a dependency of another, systemd will automatically start it. This is one of the first features it learnt. You either did something wrong, or found a very serious bug.
From what I see, the service was originally declared in a preset, not a target: !6050 (diffs)
Presets are a mechanism to declare a policy (what should be enabled an what should be disabled). You need to explicit enforce it in some way. If you want to enforce all presets, then you need to configure your default/fallback to ignore, so you don't mess with manually enabled/disabled user services.
Targets are like pseudo-services: they're started at runtime, and their dependencies are resolved in that moment, not ahead of time. So if you add a new dependency, it's started the next time that the target is started.
Targets are probably easier for this use case. Their main caveat is that user can't systemctl disable anything that's being pulled by the target, they need to systemctl mask it instead.
I understand your issue; you want to ship a preset file with some services, and have that preset automatically execute when installed, but don't want to further enforce that policy after the initial installation.
It sounds to me like you need more granularity in trigger=; you only want it to trigger when a file is installed an not upgraded. Kinda like how Arch implements them: https://man.archlinux.org/man/alpm-hooks.5.en#TRIGGERS
In any case, I still think it's worse to shove more stuff into device/UI packages (like we do for openrc), because it has been an ongoing source of integration bugs in pmOS (on openrc)
WantedBy in the [Install] section indicates where to install the package when it is explicitly enabled.
I.e.: "when this package is explicitly installed by the user, install it as wanted by the given service".
Wants= in the [Unit] section indicates that a service depends on another. When the declaring service is started, the dependant service is automatically started too.
I'd just like to point out that man systemd.unit mentions:
The preferred way to create symlinks in the
.wants/ or .requires/ directories is by specifying the dependency in [Install] section of the target unit, and creating the symlink in the file system with the enable or preset
commands of systemctl(1).
If we used targets for each UI and device/soc, users cannot use systemctl disable foo.service to disable it from starting, correct? They'd have to mask it? (I think this kinda deviates from the 'normal' way users expect to interact with service management on systemd)
The preferred way to create symlinks in the .wants/ or .requires/ directories is by specifying the dependency in [Install] section of the target unit, and creating the symlink in the file system with the enable or preset commands of systemctl(1).
Typically, services declare "when I'm enabled, I want to be a dependency of X". The user then gets to device if the user will be enabled. The documentation is referencing this common scenario.
What you're trying to do here is quite unlike the usual: you're trying to declare "for my given target, also include these other services as dependencies and enable it implicitly".
If we used targets for each UI and device/soc, users cannot use systemctl disable foo.service to disable it from starting, correct? They'd have to mask it? (I think this kinda deviates from the 'normal' way users expect to interact with service management on systemd)
Yeah, that's an annoyance.
IMHO, your best bet is to determine if something like the following is feasible for trigger=:
It sounds to me like you need more granularity in trigger=; you only want it to trigger when a file is installed an not upgraded. Kinda like how Arch implements them: https://man.archlinux.org/man/alpm-hooks.5.en#TRIGGERS
IIRC, I've seen some hacks in some packages to work around the fact that this isn't a thing.
This should allow you to trigger presets when they are installed (and only when they are installed), while using an apk functionality that is generic.
BTW: a minor upside of presets is that someone can use systemctl preset (without further arguments) to get a kind of "factory reset" for their current setup.
IMHO, your best bet is to determine if something like the following is feasible for trigger=:
This should allow you to trigger presets when they are installed (and only when they are installed), while using an apk functionality that is generic.
Ya I'm trying to remember if there was another reason why triggers wouldn't work... we did talk about it again last week (@pabloyoyoista advocated for using triggers at the time), we determined that triggers only tell us dirs and not files and maaaybe there was something else?
It would be great if we can use triggers and completely avoid the abuild install script stuff AND still meet our requirements of allowing us/downstream to specify and apply service startup prefs on service install in a way that's very simple to maintain in aports/pmaports pkging.
BTW: a minor upside of presets is that someone can use systemctl preset (without further arguments) to get a kind of "factory reset" for their current setup.
I still think we want to use presets for the reasons/requirements I stated above, and stuff like this. It helps us later implement a 'factory reset', as you pointed out
Sorry I'm late to the party. Only saw this after !6278 (closed)
The main problem with a trigger is that users that do systemctl disable will find a service that we enable by default re-enabled on each update. Contrary to what I was hoping, there seems to be no way to easily detect if a service is disabled due to the preset of due to user preferences. Having that option would enable not re-enabling the services that users disabled.
Another reason is that de-install service allows stopping services when they are removed. This is good, and something that I don't think is possible with triggers yet, since one does not get a list of the files that were removed (this maybe could be added to APK, but we haven't even pitched it)
Also just to chime in here. Having a device-specific service would run directly counter all our efforts to become a more generic distribution. For that reason alone I don't think it's the right path forward.
Just discussed this with @craftyguy and we have a new approach now, which should at least be the easiest in terms of what we need to change in abuild. All that is needed there is the patch we already had that puts systemd service files automatically in a split package, which is what upstream was fine with.
abuild change: split systemd services and related files into -systemd subpackages automatically
nothing else - no automatic post-install/post-deinstall logic!
apk-tools change (we can carry this downstream until it is heavily tested and then upstream it to alpine if they are fine with it):
extend trigger code to set environment variables LIST_OF_FILES_ADDED and LIST_OF_FILES_REMOVED when calling triggers (env vars could have a different name, but that's the idea)
pmaports packaging change: add a trigger script that uses the env vars to call correct preset command on install and removes symlinks on uninstall (for the most part we already have this logic)
Fallback solution in case we should not be able to implement the apk-tools change for whatever reason (which I highly doubt): we could combine the abuild change with !6278 (closed)
I already mentioned it over in matrix but https://github.com/systemd/systemd/issues/33373 would seem very interesting for you. While initially filed for image based distributions, the underlying idea (trigger-less unit presetting) would also be really helpful here and eliminate the need for any changes apart from creating *-systemd subpackages
I think modifying apk-tools could work. The main concern with triggers is that we wouldn't be able to handle reloading or restarting services that need it (e.g. dbus), but I think we can work around this by having apk also do stuff on package upgrade.
If we're going to modify apk-tools, I would propose just adding some additional metadata to the package and using that. One could define variables in the APKBUILD systemd_needs_reload="dbus.service", systemd_needs_restart="foobar.service" to trigger appropriate behaviour.
And at that point, apk-tools itself can directly run systemctl daemon-reload if any package touches /usr/lib/systemd (something we don't do now but really need to!) as well as reloading/restarting the individual services for packages that get upgraded. And hey it can run systemctl preset too, why not.
tbh, building apk-tools against libsystemd and doing it this way sounds drastically easier than anything else. We can either do it all directly in apk-tools (specifically that we run commands after installing/upgrading packages that touch /usr/lib/systemd, and do a systemctl daemon-reload right at the end before running triggers) OR we implement a hook mechanism (like triggers but run for each package) in addition to the trigger changes suggested above.
We cannot just ignore service reload/restart, and we should stop ignoring the need to run systemctl daemon-reload as well.