Any way to mount in a systemd --user unit?

Context: I’m trying to share my user’s pipewire session socket with non-root libvirt.

I have this working perfectly with a bind mount like this:

  fileSystems."/srv/win10/pipewire-0" = {
    device = "/run/user/1000/pipewire-0";
    depends = [
      # mounting this fails absolutely without root fs, still needs socket from service
      "/"
    ];
    fsType = "none";
    options = [
      "bind"
      "rw"
      # regular users may mount this
      "user"
      # do not mount automatically
      "noauto"
    ];
  };

where /srv/win10 is a directory with the ownership permissions needed. The fstab entry /run/user/1000/pipewire-0 /srv/win10/pipewire-0 none bind,rw,user,noauto 0 0 is created, and my user can mount /srv/win10/pipewire-0 without any privilege escalation, and when it is mounted my vm audio is perfect.

Ideally I’d like this to be done automatically after the pipewire socket is created, so I’ve created this user service:

  systemd.user.services.share-pipewire-socket = {
    description = "share-pipewire-socket";
    partOf = ["pipewire.service"];
    wantedBy = ["pipewire.service"];
    wants = ["pipewire.socket"];
    after = ["pipewire.socket"];
    serviceConfig = {
      Type = "oneshot";
      ExecStart = "${pkgs.util-linux}/bin/mount /srv/win10/pipewire-0";
      ExecStop = "${pkgs.util-linux}/bin/umount /srv/win10/pipewire-0";
      RemainAfterExit = true;
    };
  };

Despite the user option on the fstab entry, I’m getting the following error message: mount: /srv/win10/pipewire-0: must be superuser to use mount.

Hoping there’s something silly I’m missing here.

You should use a mount unit instead of a service unit.

(Fun fact. Systemd translates that fstab entry you made to a user mount unit directly)

Two things you can do:

  1. Add the unit dependency to the fstab entry. You can set x-systemd.after= and x-systemd.wants= option on the fileSystems option.
  2. Write the mount unit by hand (can systemctl cat the generated mount unit as a starting point)
2 Likes

Oh on further thought I’m not sure if this works? The pipewire socket will run in the user service manager whilst the mount in the system service manager. Argh

I’m not even sure if mounts work on the user service manager. But there doesn’t seem any docs about this. Odd.

It might works in option 2 if you make the mount unit a user unit (systemd.user.mounts NixOS option) but I’m wondering if you’ll get the same permission error then too

Yeah, it’s a bit of a pickle with the fileSystems."*" generated mounts being a system unit, and pipewire.socket being a user unit.

I think systemctl link ... can create relationships between the two, but I don’t see a declarative way to do that in the NixOS options either, and I think that might get really confusing since the system level pipewire.service and pipewire.socket still exist even if they’re inactive.

If there aren’t permission issues, I think the systemd.user.mounts options is probably best. I’ll have to try it eventually. I might just tie it to my window manager startup as a placeholder, but I already know from experience that those can be executed before the pipewire socket is available, and I don’t love sleep/retry strategies.

Okay, did some digging today. Turns out home-manager has a systemd.user.mounts option (generated mount files at $HOME/.config/systemd/user/*.mount, but that still results in the same permission issues.

I did notice that the user option under fileSystems specifies that it allows “an ordinary user” to mount the filesystem, and started trying to play around with specifying User=... in the service and mount units, but this resulted in systemd errors complaining about invalid groups that I couldn’t find a resolution for regardless of what Group=... configs I provided in the same unit (tried users, my users’s gid, a new group created specifically for this purpose…).

While looking at those options, I did notice that the fileSystems option users allows “any user” to mount the mount, and that works perfectly with this config:

  # share pipewire socket with qemu-libvirtd via bind mount
  fileSystems."/srv/win10/pipewire-0" = {
    device = "/run/user/1000/pipewire-0";
    depends = [
      # mounting this fails absolutely without root fs, still needs socket from service
      "/"
    ];
    fsType = "none";
    options = [
      "bind"
      "rw"
      # any user may mount this
      "users"
      # do not mount automatically
      "noauto"
    ];
  };

  # mount upon user pipewire.socket
  systemd.user.services.share-pipewire-socket = {
    description = "share-pipewire-socket";
    partOf = ["pipewire.service"];
    wantedBy = ["pipewire.service"];
    wants = ["pipewire.socket"];
    after = ["pipewire.socket"];
    serviceConfig = {
      Type = "oneshot";
      ExecStart = "${pkgs.util-linux}/bin/mount /srv/win10/pipewire-0";
      ExecStop = "${pkgs.util-linux}/bin/umount /srv/win10/pipewire-0";
      RemainAfterExit = true;
    };
  };

This does have the side effect of allowing any random service user to disable my VM audio, but I’m okay with that risk. There are definitely a few things I still feel puzzled about here, and I think there could still be some “perfect” answer out there, but this is good enough for me.

Thanks for your reply! It prodded me towards a workable path.

3 Likes