OpenRC / openrc

The OpenRC init system
BSD 2-Clause "Simplified" License
1.47k stars 247 forks source link

"User services" in OpenRC #432

Open NyaomiDEV opened 3 years ago

NyaomiDEV commented 3 years ago

It would be nice if users could have and handle their own services and runlevels.

Nowadays a lot of applications rely on services run as the current user (for example sound servers like PipeWire and PulseAudio are required to run multiple applications that play audio in a tradiitonal desktop configuration).

For comparison, Systemd provides users with a mechanism to have their services (units) run and supervised upon login. This is, in turn, used by many desktop environments and also daemons (PulseAudio, PipeWire, etc.)

Some other init systems, like Runit, have no concept of user services, but they can be easily "tricked" to effectively handle them (Example).

OpenRC does not have any concept of user services and it doesn't seem to be easily adaptable to handle them in a tricky way; the best an user can do is effectively modify some system services so that they are run under their user and group and then they can write a bunch of sudo rc-service <service> start lines on their .profile so that the services actually get started and supervised. This works but it is tricky and it requires the user to set a NOPASSWD rule for rc-service on their sudoers file, which makes for a security problem.

Hence, it is probably wiser to let OpenRC have a concept of user services, in a way or another; the proposed solution would be:

I know this may be sloppy as a proposed solution, but I hope to find more people willing to discuss this further.

mazunki commented 3 years ago

Better solution

While I agree, user services is something I am looking for, I have some notes:

What I've done on my machine is to create a runtime for my user, and add all the services i want for my user into it (+default), and manually setting command_user to my user.group for the services, and hardcoding some paths. I don't know of a current way to make this cross-user compatible.

Then the only command I need to run is doas openrc mazunki, and it will run the services for me (including PipeWire).

Suggestion

The way I can see this working is if users had their own runtime (not completely sure about the path or naming conventions here, although username == runtime wouldn't be the worst idea, although this could create some naming conflicts if the username is something like boot or default), and be able to add/remove scripts to it without escalating permissions.

Then, in scripts, we'd need to have access to the $USER and maybe $USERGROUP and maybe some of their XDG variables. With that, it would be trivial to run ${XDG_CONFIG_HOME}/environment to get other variables.

NyaomiDEV commented 3 years ago

What I've done on my machine is to create a runtime for my user, and add all the services i want for my user into it (+default), and manually setting command_user to my user.group for the services, and hardcoding some paths. I don't know of a current way to make this cross-user compatible.

Then the only command I need to run is doas openrc mazunki, and it will run the services for me (including PipeWire).

This still poses a security risk as you could omit command_user and have stuff run as root; that should be defaulted for and enforced by OpenRC itself; also, unless you love to put passwords in twice, I definitely see how that doas could be inconvenient (unless you plug in a special rule for it, but again, security vulnerability)

The way I can see this working is if users had their own runtime (not completely sure about the path or naming conventions here, although username == runtime wouldn't be the worst idea, although this could create some naming conflicts if the username is something like boot or default), and be able to add/remove scripts to it without escalating permissions.

The idea of runlevels for users is not a bad one, though the best thing would be to prefix it in some way: bob => runtime user.bob

Then, in scripts, we'd need to have access to the $USER and maybe $USERGROUP and maybe some of their XDG variables. With that, it would be trivial to run ${XDG_CONFIG_HOME}/environment to get other variables.

Correction, the variables to be exposed would be at least $USER, $UID, $GROUP (not usergroup, that does not exist), $GID and $HOME. I am saying at least; you'd want to have some more variables or your entire environment available, though even that is a security risk; I'd propose a flag like inherit_user_env to inherit the environment in some way. Also I do not have ${XDG_CONFIG_HOME}/environment in my system and therefore I am not sure if you are suggesting something new to be added for users.

mazunki commented 3 years ago

This still poses a security risk as you could omit command_user and have stuff run as root; that should be defaulted for and enforced by OpenRC itself; also, unless you love to put passwords in twice, I definitely see how that doas could be inconvenient (unless you plug in a special rule for it, but again, security vulnerability)

I haven't looked into the security concerns regarding OpenRC. I am a single-user on my desktop, so it hasn't been a concern for me.

It would be better to run openrc user.mazunki, without root, and let it run up the missing services for user.mazunki, automatically setting command_user as needed. I think this would be a better solution, instead of needing to hard-code command_user on all user services.

The idea of runlevels for users is not a bad one, though the best thing would be to prefix it in some way: bob => runtime user.bob

I like it, I changed it on my system.

Correction, the variables to be exposed would be at least $USER, $UID, $GROUP (not usergroup, that does not exist), $GID and $HOME. I am saying at least; you'd want to have some more variables or your entire environment available, though even that is a security risk; I'd propose a flag like inherit_user_env to inherit the environment in some way. Also I do not have ${XDG_CONFIG_HOME}/environment in my system and therefore I am not sure if you are suggesting something new to be added for users.

I just named it $USERGROUP as an example, but I think maybe $RC_USER and $RC_GROUP would be better options. I'm not sure we need the UID, GID, and HOME path, but perhaps.

Maybe setting a list of environment variables to be inherited would be okay. Having XDG_* by default would sure be nice.

Being able to inherit_user_env would be an appendix to those, I think.

You don't have ${XDG_CONFIG_HOME:-$HOME/.config}/environment on your system, because it's not a default thing to be there. I think the analogous "default" one would be ~/.profile, but personally I hate having dotfiles in $HOME, so I've cleaned up my system a bit. Similarly, I also have ${XDG_CONFIG_HOME}/{aliases,dircolors,x11-environment,wl-environment}. It makes sense to have user environment in the config path, since we also have /etc/environment for system-wide settings.

mazunki commented 3 years ago

I have posted a "working" example for how I update my CloudFlare DNS records on my scripts repository as a user. It seems to work well, except for the fact that I still need to use root to change runtimes.

I have also tried to boot up my window manager through it, but I am struggling with running dbus manually, without dbus-run-session. PipeWire does work fine, though (on one session, but not for my secondary tty running an Xorg session).

I can't find a clean and proper way to pass around variables from a single node, down to the rest of the environment, though. It would be nice if it were possible to export the entire environment at once.

NyaomiDEV commented 2 years ago

@mazunki I just had some time to check your scripts folder, and unless I am mistaken you are not really solving the main issue: OpenRC is still running those services as system services and you still need root to manage them.

Sadly I didn't come up with a better or more dynamic idea as of yet (also I think there'd be another problem -- how can we tell the exact moment when users log in and out to start and stop their services?) and I also resorted to system services with command_user.

Hope this issue gets some traction.

mazunki commented 2 years ago

@mazunki I just had some time to check your scripts folder, and unless I am mistaken you are not really solving the main issue: OpenRC is still running those services as system services and you still need root to manage them.

Sadly I didn't come up with a better or more dynamic idea as of yet (also I think there'd be another problem -- how can we tell the exact moment when users log in and out to start and stop their services?) and I also resorted to system services with command_user.

Hope this issue gets some traction.

You are entirely correct.

My "solution" involves initial escalation, although this is only a concern on multiuser scenarios. For my home usage I don't really mind, since I'm not giving pipewire, for instance, any escalation, meaning there's no threat of the apps posing a security risk. The only threat is me abusing OpenRC to take over my own computer.

It's a compromise since there's no better solution yet.

Also, not only that, I also need to hardcode a configuration file + init script for each user, with the appropriate RC_USER variable. That's fine if only one user needs the user service, but under most scenarios I would like to be able to add the user service to multipe running user instances at once. Currently, my solution would require copies of the same user-pipewire init script to solve this, instead of simply having config differences.

NyaomiDEV commented 2 years ago

how can we tell the exact moment when users log in and out to start and stop their services?

Replying to myself: OpenRC's PAM integration. a PAM module can be indeed used to know when an user logs in and out of the system.


By the way, for now Dinit in userspace works just fine on my Artix install, until user mode OpenRC gets some more attention.

thesamesam commented 2 years ago

how can we tell the exact moment when users log in and out to start and stop their services?

Replying to myself: OpenRC's PAM integration. a PAM module can be indeed used to know when an user logs in and out of the system.

By the way, for now Dinit in userspace works just fine on my Artix install, until user mode OpenRC gets some more attention.

Yeah, this looks really interesting. For others, see https://github.com/chimera-linux/dinit-userservd too.

etbuira commented 2 years ago

For what it worths, i use this initscript:

#!/sbin/openrc-run
# Copyright 2021 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

name="userd daemon"
description="user specific init and shutdown spawner"

user_name=${user:-${RC_SVCNAME#userd.}}
user_homedir="$(getent passwd ${user_name} | cut -d: -f6)"
user_userd="${user_homedir}/.userd/"
shutdown_timeout=${shutdown_timeout:-"5m"}
pidfile="/var/run/${RC_SVCNAME}.pid"
SSD_IONICELEVEL="${ionice}"
SSD_NICELEVEL="${nice}"

start() {
        start-stop-daemon --exec "${user_userd}/start" --pidfile "${pidfile}" --user="${user_name}" --background --chdir="${user_userd}" --make-pidfile --stdout "${user_userd}/stdout.log" --stderr "${user_userd}/stderr.log"
}

stop() {
        timeout "${shutdown_timeout}" su --login -c "cd ${user_userd} ; ${user_userd}/shutdown" - "${user_name}"
}

Then, administrator usually only have to cd /etc/init.d; ln -s userd userd.username, add it to wanted runlevel, and this uid will have ~/.userd/start and shutdown called appropriately (those can be used to daisy chain a specific init-style program, like svscan)

viasux commented 2 years ago

This is an issue for me, I have a service for syncthing, but it has to run as root (it literally gives you a warning for doing this, as it could be problematic) because openrc doesn't support a user runlevel.. pretty big issue imo.

amano-kenji commented 2 years ago

I launch dinit through emptty. Emptty closes dinit automatically when a login session is closed.

~/.config/emptty

#!/bin/bash
Selection=true

xrdb -merge ~/.Xresources

. /etc/profile
. ~/.env

dinit &

"$@"

dinitctl shutdown

When a login session starts, sway executes this command to make dinit aware of GUI environment it is in.

dinitctl setenv DISPLAY XAUTHORITY WAYLAND_DISPLAY

~/.config/dinit.d can contain user services.

WhyNotHugo commented 2 years ago

There's two distinct (but very similar) ideas being discussed here:

For the second approach, OpenRC would need to essential do something like:

if getuid() == 0:
    INIT_DIR = /etc/init.d
else:
    INIT_DIR = ~/.config/init.d/

And a few similar changes for the other paths. I did something rather similar to OpenBSD's rc about a decade ago and the result was a bit of a hack but worked.

Being able to manage user services like this would be very convenient to manage services like pulseaudio, pipewire, xdg-portals, offlineimap, etc.

amano-kenji commented 2 years ago

Maybe, we should dinit instead of OpenRC for user services?

AtelierSnek commented 1 year ago

Adding our support here for use-cases like rootless containerd (and friends). You can currently get them into a service by using command_user, but as mentioned, there's risk of escalation, managing the scripts gets annoying (you need to bug the admin), and so on. This would make everything a whole lot easier.

Forza-tng commented 1 year ago

For the second approach, OpenRC would need to essential do something like:

if getuid() == 0:
    INIT_DIR = /etc/init.d
else:
    INIT_DIR = ~/.config/init.d/

And a few similar changes for the other paths. I did something rather similar to OpenBSD's rc about a decade ago and the result was a bit of a hack but worked.

Being able to manage user services like this would be very convenient to manage services like pulseaudio, pipewire, xdg-portals, offlineimap, etc.

I am in favour of this solution if it can be implemented good.

Some extra thoughts