Expose a more consistent subset of systemd's service directives

+1 to grouping services and being able to turn them on and off. For battery operated devices being able to have the ability to reduce the power load on the system is great. Having groups to only keep core services running would make this much easier.

Would that be something like, say

apps:
  foo:
    command: powerhungryd
    daemon: simple
    group: powered
    procrastinate: true
  bar:
    command: moarpowerhungryd
    daemon: simple
    group: powered
    procrastinate: false
  baz:
    command: statusd
    daemon: simple

and then somehow arange for

snap services start mysnap.powered

to run when on power?

I can see the value of procrastinate (ok so I couldn’t find a suitable antonym for start, i’m really bad at names, but this needs to be something that means “don’t start”, as the default needs to be false). The grouping thing seems less useful, given snap services start can already take a list, but I do see the convenience. Not sure it’s worth the complexity it introduces (but maybe I’m overthinking it)

or maybe I misunderstood, and by grouping you mean to have predefined targets, and the ability to flag services to depend on those targets? Like, say, we create a ac.target that’s only reached if the system is on AC¹, and then

apps:
  foo:
    command: powerhungryd
    daemon: simple
    group: ac

means that powerhungryd would only run when on AC?


  1. much handwaving here as I’m not sure that would properly handle going off the group, but it’'s doable behind the scenes one way or another

Another interesting option would be WatchdogSec= in order to utilize the watchdog feature from systemd to automatically restart a service when it doesn’t respond to systemd anymore. See here for details.

2 Likes

Yeah, targets would be the ideal implementation of solving grouping and being able to launch or kill a group of services depending on system state. I don’t know how you would implement it, but keeping the keywords to be able to start and stop targets like individual services can be in their related grouping would be great, eg. Before=, After=, WantedBy=, Wants=, RequiredBy=, Requires=, PartOf=, BindsTo=:

This ties into service control with snapctl as well, I suppose.

It’d also be nice to support user vs. system services. That might be orthogonal to adding these other options though.

Yes @morphis, systemd watchdog support is on my list but a little bit later (as we need to look into whether we’ll need to tweak apparmor because of sd_notify for the watchdog).

@cratliff I’m trying to understand if grouping would necessarily involve grouping with things outside of the snap, or if it’s just (or primarily) within the same snap. Could you clarify that?

Or maybe we can meet and talk about that; are you still around?

I am primarily thinking of things internal to the snap. There may need to be something external, such as multi-user.target if the snap doesn’t run anything similar internal, but that can probably be handled internally somehow.

FYI, this is on my list and not forgotten, but behind a few things.

1 Like

Hello. We are currently trying to adapt our ROS-based robot to use Snappy Core. Since ROS is highly componentized, we publish around 20 different ROS services from our snap, which have ordering and dependency requirements. We also use .target files to group services.

New Directives that we need include:

Before=, After= For ordering, as mentioned in the OP.

WantedBy=, Wants=, RequiredBy=, Requires=, PartOf=, BindsTo=
These specify dependencies between service and, especially between targets and services. PartOf= enables the services that are started by a target to also be stopped if that target is stopped. BindsTo= allows us to start & stop the ROS service that interfaces with a hardware device whenever the hardware appears or disappears.

ConditionPathExists=
We use this to enter to start a different set of services if we have not yet been configured. For example, don’t start our main application node yet but do start a service to download configuration information from the cloud. Although this is the only Condition… directive that we currently use, I can imagine that most of the others are equally useful.

Environment=, EnvironmentFile=
Some behavior in ROS launch files can only be configured using environment variables. Note that the current ability to specify an environment variable in snapcraft.yaml only accepts hard-coded values as far as I know. We need to load them from a config file.

Conflicts=
This is helpful for systemd target-based mode switching, in cases where modes are mutually exclusive. For example, higher-energy ROS services would Conflicts= with our low-power.target, and our “normal.target” will Conflict= with our low-power.target

Alias=
We use this for debugging, by specifying all dependencies via an aliased service name. Thus we can disable the normal service and enable a different one with the same alias to make a substitute. The substitute may compiled with debug flags and no optimization, or run under gdb, strace, etc.

As you can imagine, lack of support for these made porting to Snap a very big challenge. So far, we have to run a configure hook that rewrites the .service files, which is very messy and totally breaks confinement. Supporting these directives in snapcraft + snapd will be a HUGE plus!

Thanks

2 Likes

Ditto. We do not like running our services as root but wish to use a system user. I am neutral about whether all we need is support for the User= and Group= directives, vs. launching an instance of systemd --user. The former seems simpler.

1 Like

Systemd has explicit support for that in the form of the ConditionACPower= directive.

I’m in the process of allowing snaps to privilege drop to the ‘daemon’ user/group. After that, we’ll be implementing support for using other snapd-managed users. Perhaps User= and Group= could be added after that, but I’d prefer these directives not be added until this other feature is implemented.

2 Likes

My particular use case would be desktop session services, so the latter is what I’m after personally. For that use case, it isn’t so much about running as a non-root user as running in the context of the desktop session (and potentially having multiple instances if there are multiple desktop sessions).

@hcochran this is exactly he sort of feedback I was hoping for, so thank you very much for sharing your findings. I’ll be discussing your post here and replying in detail later in the day, but didn’t want to let more time pass without saying thank you.

Just to update y’awl on where we stand, we’re currently struggling with Wants/Requires/PartOf/BindsTo, trying to find a sane way to expose these. Our problem with the names systemd uses for them is that we find it impossible to remember which directive has which exact semantics, and don’t want to pass this same feature to the snapd world, if at all possible.

Before and After are fine, and clear, and I think uncontroversial.

Conditions, yes we should expose all the relevant ones. Probably export it as a conditions map inside an app, to keep it neat, and probably implemented using the AssertFoo instead of the ConditionFoo, so we have logs (let us know if there’s a reason not to do things this way).

Environment is already supported; we don’t think there’s value in additionally supporting EnvironmentFile, unless there’s an actual use case for it where the file lives in the snap’s data directory and is modified? It sounds rather far-fetched, so let us know if this is the case. If you’re using it to read a file in the snap, snapcraft should be able to pack an environment file into environment stanzas (but this isn’t done yet). If you’re using it to read a file outside of the snap entirely, we wouldn’t want to support that (at least not without a pertinent interface, and that would need further design).

Conflicts and PropagatesReloadTo fit into the bigger conversation we’re having about Requires and etc.

@hcochran is PartOf really useful on its own in practice, given that you can’t really define targets? Or would you also need targets for it to be useful? (I don’t think we want to allow targets – but on the other hand if a dev needs them they’ll end up simulating them with an app that does nothing, so maybe we do).

Is the issue just the naming convention? I can understand the desire to simplify wherever possible, but coming from using systemd it would be likely be more confusing to have to remember the mapping for systemd->snap command names, then when attempting to debug the service remembering the opposite direction to remember what to change in the snapcraft.yaml file to modify the service. For a more complex system a transparent passthrough would likely be much less confusing.

That’s pretty close to the situation we are in. We are adding the targets ourselves after the snap installs. This is currently one of the things preventing us from moving to a confined snap. What is the reason for not wanting to allow targets?

1 Like

Systemd is extremely well-documented and is a defacto standard in the Linux world (previous controversies notwithstanding). Therefore, I believe the meanings of these things will only become more widely known with time. I strongly agree with cratliff that mapping systemd names to a different set of names would exacerbate, not help the problem. (This comment applies to the naming of the Condition* directives, although I am unaware how renaming them to Assert* is connected with logging.)

I also think that WantedBy, Wants, RequiredBy, Requires are in very common use with meanings that are fairly intuitive. That leaves BindsTo and PartOf which, while less obvious, are also very useful. In fact, I think BindsTo is basically essential for any product that has hardware which may come and go dynamically and for which you need associated software to start and stop when this happens. It seems to me that this would apply to many embedded, robotics, & IoT-type devices.

The way it works is this:
Gadget snap would install a udev rule like this one:

SUBSYSTEM=="usb", ATTRS{idVendor}=="BEEF", MODE:="0666", SYMLINK+="AwesomeCamera", TAG+="uaccess", TAG+="udev-acl", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/AwesomeCamera"

This causes a dynamically-generated systemd unit called “dev-AwesomeCamera.device” to activate whenever this USB device appears and to deactivate when it goes away.

Then, we have a service (in our case a ROS node) that will start and start automatically when this device appears or disappears by adding this to its service file:

[Unit]
BindsTo=dev-AwesomeCamera.device

Without BindsTo=, we would have to use some out-of-band way to notice the removal of the device and stop the corresponding service. This involves some wheel reinvention and may even require polling.

Does that clarify how needed this may be?

Thanks, very much, for considering our feedback.

1 Like