Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

systemd user-units can't be easily enabled/disabled per user #21460

Open
ToxicFrog opened this issue Dec 28, 2016 · 17 comments
Open

systemd user-units can't be easily enabled/disabled per user #21460

ToxicFrog opened this issue Dec 28, 2016 · 17 comments

Comments

@ToxicFrog
Copy link
Contributor

ToxicFrog commented Dec 28, 2016

The unit files emitted by systemd.user.services.* don't have an [Install] section, so users can't* enable/disable it with systemctl --user (enable|disable) (they can still create the ~/.config/systemd/user/default.target.wants/ symlinks by hand if needed).

It might seem that you could fix this in configuration.nix with something like:

  systemd.user.services.syncthing.wantedBy = [ "default.target" ];

But that turns it on for all users, including root(!) and sddm(!!).

I know NixOS doesn't use [Install] in general, but for user-units, which are meant to be used and, to some extent, configured by individual, nonprivileged users, it makes sense to permit it; furthermore -- although I'm not sure what a good way to accomplish this -- it would be nice if the system administrator had some way of saying "auto-enable this user-unit only for the following users".

@moretea
Copy link
Contributor

moretea commented Dec 29, 2016

There might be some related work in #9250 I would be interested in seeing this progress as well.

@joachifm joachifm mentioned this issue Jan 2, 2017
7 tasks
@ToxicFrog
Copy link
Contributor Author

Also, for the record: users can do the install "by hand" with:

mkdir -p ~/.config/systemd/user/default.target.wants
ln -s /etc/systemd/user/<unit name> ~/.config/systemd/user/default.target.wants/

which is all that 'systemctl --user enable' is really doing under the hood.

@pjones
Copy link
Contributor

pjones commented Sep 6, 2017

I recently tried sddm and this issue bit me. All user services started for the sddm user, and continued to run even after I logged in. This kept the same services from starting for my user account due to DISPLAY (compton) and ports already in use (mpd), etc.

Can we create systemd user target that is started when "real" users log in and have all NixOS user services use partOf to depend on this new target?

@ghost ghost mentioned this issue Mar 16, 2019
22 tasks
@ghost ghost mentioned this issue May 18, 2019
10 tasks
@ghost ghost mentioned this issue Dec 21, 2019
10 tasks
@ghost ghost assigned ghost and unassigned ghost May 18, 2020
@ghost
Copy link

ghost commented May 18, 2020

I see that current behaviour for 20.03 is that user services enabled for all users.
Previous behaviour was so that user services are disabled, until user makes a link with hand

@ToxicFrog
Copy link
Contributor Author

Oh dear.

@stale
Copy link

stale bot commented Nov 15, 2020

I marked this as stale due to inactivity. → More info

@stale stale bot added the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Nov 15, 2020
@florianjacob
Copy link
Contributor

still important to me

@stale stale bot removed the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Nov 16, 2020
@stale
Copy link

stale bot commented Jun 4, 2021

I marked this as stale due to inactivity. → More info

@stale stale bot added the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Jun 4, 2021
@TLATER
Copy link
Contributor

TLATER commented Aug 9, 2021

This caused some confusion here since pipewire is launching on the root user, even though it won't get access to a dbus instance and therefore do nothing: https://discourse.nixos.org/t/cannot-get-sound-to-work-at-all-please-help/14453

@stale stale bot removed the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Aug 9, 2021
@petrosagg
Copy link
Member

I've also just hit this problem. My usecase is that I have a set top box on which I want to run an application that plays audio (so it needs pulseaudio, which needs a non-root user). The end state should be a user systemd service that is only enabled for a specific user. I've tried numerous things and right now I settled for this very suboptimal solution:

  # TODO: do this declaratively
  system.activationScripts.multimedia.text = ''
    rm -rf /home/multimedia/.config/systemd
    mkdir -p /home/multimedia/.config/systemd/user/default.target.wants
    ln -s /etc/systemd/user/taped.service /home/multimedia/.config/systemd/user/default.target.wants/
    chown -R multimedia:multimedia /home/multimedia/.config/systemd
  '';

I'm running pipewire-pulse to provide the pulseaudio socket so pulseaudio.systemWide isn't an option (nor desirable).

@stale stale bot added the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Jul 31, 2022
@ppenguin
Copy link
Contributor

ppenguin commented Oct 7, 2023

AFAICT this is still relevant?

The UX of defining an auto-enable and auto-start user service could be improved. Maybe we could add an optional wantedByFor that works like this:

wantedByFor = [ "default.target" ]  [ "user1" "user2" ];

The submodule logic would then override wantedBy with wantedByFor if specified, and apply accordingly.

Or, alternatively have an installFor [ "user1" "user2" ] that takes a list of user names and/or maybe the special keywords (as string) systemUsers or normalUsers

@stale stale bot removed the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Oct 7, 2023
@ToxicFrog
Copy link
Contributor Author

I'm running pipewire-pulse to provide the pulseaudio socket so pulseaudio.systemWide isn't an option (nor desirable).

This is not a general solution to this problem, but for this specific use case, where you have a systemd service that you want to start on boot and run as a single specific user, you can just do something like:

systemd.services.taped.serviceConfig = {
  User = "multimedia";
  Group = "users";
};

I.e. don't use a user-unit at all, use a system unit that runs as that user. I've used this approach for both snapclient and calibre-server in the past. Depending on what taped does, you might also need to add some stuff to its environment.

This bug is primarily a problem when you want to declare a user-unit, and want to run it for >1, but not necessarily all, users, and/or want users to be able to manage it themselves using systemctl enable, neither of which are the case for you.

@ToxicFrog
Copy link
Contributor Author

AFAICT this is still relevant?

Yep.

Or, alternatively have an installFor [ "user1" "user2" ] that takes a list of user names and/or maybe the special keywords (as string) systemUsers or normalUsers

I like this one more -- wantedBy is orthogonal to which users the unit is enabled for. The special-keywords

I think a completed version of this would look something like:

systemd.services.foo = {
  # Choose who to enable the service for declaratively; the service will be instantiated
  # for these users and started on login (by default) or on boot (for users with linger enabled),
  # subject as always to wantedBy and similar settings.
  enableFor = [ "user1" "user2" ];  # list of users to enable the service for
  # enableFor = "systemUsers";  # or all system users
  # enableFor = "normalUsers";  # or all normal users
  # enableFor = "allUsers";  # or everyone
  # Control whether users can easily enable the service with `systemctl --user enable`
  # Note that this is not a security boundary; if this is off users can still enable it
  # on an individual basis using `ln -s`.
  allowUserEnable = true;
};

I'm honestly not sure if the latter setting should exist at all or if we should just unconditionally emit an [install] section.

I'm also not sure how enableFor would actually work under the hood. Normally you do that by writing symlinks into the user's ~/.config/systemd directory. I don't know if there's somewhere you can put them outside ~ but still organized per user (i.e. not enabled for all users). It may be that enableFor is not practical apart from a very coarse enableForAllUsers = true/false knob, and for more fine-grained control we need to emit [Install] and then either manage the per-user settings mutably (with systemctl --user enable) or using home-manager.

@ToxicFrog
Copy link
Contributor Author

Thinking about this a bit more, and refreshing my memory on what the module looks like:

  1. if you set enabled = false, an appropriately named unit file is still created but is a symlink to /dev/null.
  2. if you set enabled = true but neither wantedBy nor requiredBy, the service is defined but not started. It can be enabled on a per-user basis by creating the appropriate symlinks.
  3. if you set enabled = true and also at least one of wantedBy or requiredBy, the service starts for all users.

And there are two problems here, which are kind of conflated in this bug but which, I think, are different problems:

  • in case (2), the service cannot be mutably managed by individual users using systemctl --user enable/disable. (It can still be managed but you have to do the mkdir and ln commands by hand.)
  • there is no declarative intermediate case between (2) and (3) where you can write a nixos config that enables it for some, but not all, users.

The first problem is fixed by [Install] and if I can scrape together the free time, I'll try to write a patch for it. This is the capability I actually want, so that e.g. family members can enable or disable services like syncthing on a shared machine on a self-serve basis.

The second problem involves editing the contents of the user's home directory and thus may more properly belong to home-manager than nixos.

@tek
Copy link
Contributor

tek commented Mar 3, 2024

FYI for anyone who needs a workaround:

{
  systemd.user.services.${name}.unitConfig.ConditionUser = "!@system"; # or ["user1" "!user2"]
}

@dscheuneman
Copy link

FYI for anyone who needs a workaround:

{
  systemd.user.services.${name}.unitConfig.ConditionUser = "!@system"; # or ["user1" "!user2"]
}

or ConditionGroup

@muradbu
Copy link

muradbu commented Sep 11, 2024

I'm running into this issue now on a multi-user system with user services intended to be enabled by users individually. So I'm leaving this here in hopes of coming across a solution or workaround.

After a reboot all services are enabled for every user, but not for the root user as described in the original post. System users seem to be unaffected as well.

This is unfortunate because it unnecessarily hogs resources when there are a lot of users. Manually disabling services is moot:

systemctl --user disable jellyfin
The following unit files have been enabled in global scope. This means
they will still be started automatically after a successful disablement
in user scope:
jellyfin.service

An example of such a service:

  systemd.user.services.jellyfin = {
    enable = true;
    after = [ "network.target" ];
    wantedBy = [ "default.target" ];
    description = "Jellyfin";
    serviceConfig = {
      Type = "simple";
      ExecStart = "${pkgs.jellyfin}/bin/jellyfin";
    };
  };

Removing wantedBy = [ "default.target" ]; prevents it I think (not tested with a reboot), but I think runs into what @ToxicFrog says here:

in case (2), the service cannot be mutably managed by individual users using systemctl --user enable/disable. (It can still be managed but you have to do the mkdir and ln commands by hand.)

systemctl --user enable jellyfin
Created symlink /home/murad/.config/systemd/user/jellyfin.service → /nix/store/dq5f30ik76jfwrc8r3vhwg95qhaw6vp0-unit-jellyfin.service/jellyfin.service.
The unit files have no installation config (WantedBy=, RequiredBy=, UpheldBy=,
Also=, or Alias= settings in the [Install] section, and DefaultInstance= for
template units). This means they are not meant to be enabled or disabled using systemctl.

Possible reasons for having these kinds of units are:
• A unit may be statically enabled by being symlinked from another unit's
  .wants/, .requires/, or .upholds/ directory.
• A unit's purpose may be to act as a helper for some other unit which has
  a requirement dependency on it.
• A unit may be started when needed via activation (socket, path, timer,
  D-Bus, udev, scripted systemctl call, ...).
• In case of template units, the unit is meant to be enabled with some
  instance name specified.

I'm considering the following:

  • writing services the non-nix way
  • omitting wantedBy = [ "default.target" ]; and script my own behavior which replaces systemctl enable functionality

Edit: loosely related issue. I'm going to look into per-user services using home-manager first.

I'd like to hear how others deal with this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests