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

User Services 2.0 #723

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

User Services 2.0 #723

wants to merge 8 commits into from

Conversation

navi-desu
Copy link
Member

with a proper branch now

@navi-desu navi-desu mentioned this pull request Jul 23, 2024
@navi-desu navi-desu force-pushed the user-services branch 2 times, most recently from a98a008 to c26bc8a Compare July 24, 2024 00:33
@WhyNotHugo
Copy link

Previous discussion: #573

@cnt0
Copy link

cnt0 commented Sep 9, 2024

Hello, I didn't review this PR (yet?) so I have no idea what's going on inside at all, however, I'd like to describe my setup so maybe you'll find my thoughts useful and implement it here.

So, I came to conclusion that there are in fact 2 kinds of user services:

  1. DE-independent, the ones I'd like to launch in any case, for any DE and even if there's just shell. Example: pipewire
  2. DE-dependent, the ones relying on environment variables, sockets and the like set by your DE. Example: various panels, widgets and the like. yambar, wlsunset.

For DE-dependent services, there's a problem that if DE process is not their ancestor then you'll need to pass al the required environment variables by hand. Systemd do exactly that by using command systemctl import-environment, but IMO this is very annoying and error-prone cause there's no way to know which variable you'll need right now or in the future. Not to mention synchronization. The other way is to define such variables in e.g. ~/.profile and the source this file. This is inconvenient as well, because you won't be able to define DE-specific variables this way.

So DE-dependent services must have DE as their ancestor. This way we will exterminate the problem of environment variables for good.

The 2nd problem of DE-dependent services is that they can depend on DE-independent ones. For example, my yambar panel has a volume widget which obtains this value from pipewire, so it naturally depends on pipewire.

So I'm launching 2 s6-svscan instances: one with DE-independent services via turnstile, another with DE-dependent services from within sway config. So they have access to all the required environment variables, they can depend on each other

$ cat ~/.config/s6/sway/yambar/run
#!/bin/bash -e
s6-svwait ~/.config/s6/user/pipewire
yambar

and in general, everything is very nice, simple and works very well.

So IMO for openrc user session to be useful, it should (ideally) be able to be started multiple times and provide a public API to wait for services or (systemd-like) have a mechanism of injecting environment variables like systemctl import-environment.

@WhyNotHugo
Copy link

Regarding compositor-dependant services, there are a few other approaches worth mentioning (and many other which I don't think would add any value here):

Start the service manager as a child of the compositor

This is by far the simplest approach, and I doubt there's anything simpler than this. Run the service manager as a child of the compositor. E.g.: via exec in sway's config. The service manager will inherit all necessary variables.

The main difference is that if I pick zsh as a shell instead of sway when logging in, user services won't start. For me, this is perfectly fine. If I'm logging into zsh, it's usually on a second tty to debug something that I've broken for my main session.

If you expect pipewire or some other service when logging into a console, you can configure .zprofile to start the service manager with a different runlevel. For this use case, I'd say this approach starts growing some complexity and is not as ideal.

Run the compositor as a service

Running the compositor itself as a service is a pretty straightforward of ordering dependencies. You need some mechanism to save relevant environment variables into a file, and signal readiness. Usually a tiny helper executed by the compositor is enough.

You dependant service scripts (e.g.: yambar) would depend on the compositor service and load those previously saved files with the environment variables.

There are no obvious downsides to this approach, and it results in much tighter integration between relevant parts (without any unnecessary coupling). Restarting the compositor is possible, and dependant services are properly restarted.

This works especially well with s6, where s6-svscan will be the main process invokes during login and that lives for the entire session. You'll end up with a service tree that's a pretty good reflection of your mental model of services.

This approach is likely to have issues if the service manager is started by root and not part of the user's session. In such a scenario, because the compositor is not part of the session, I believe that seatd won't grant the compositor access input/output devices.

Exporting compositor variables into a file

When the compositor starts, the relevant environment variables are stored in a file. The run scripts for dependant services load these environment variables. This part is the same as above.

In this case, the compositor doesn't run as a service, and is started via some other mechanism. When the compositor is ready, it starts a second runlevel. Services which depend on the compositor are tied to this runlevel.

@navi-desu
Copy link
Member Author

the "recommended" way here would be to switch to a runlevel while inside your compositor's session. openrc does not have a deamon of any kind to import environments to, but when starting a service it does pick up the current environment (and filters it)

so, my current workflow with that is
add rc_env_allow="WAYLAND_DISPLAY" to ~/.config/openrc/rc.conf
log in, pam will launch pam_openrc which launches the default runlevel, starting the DE-independent services (pipewire, dbus)
on my sway config, i exec openrc --user gui, which now will have access to the WAYLAND_DISPLAY and thus launch dunst and alike

openrc just using the current environment is both more convenient but also a bit more error prone than systemd's approach, but i think it works quite well for us

i do not recommend you run your compositor as a service at all. if you use pam_openrc, it would start during the pam auth sequence, not in a proper session, and as soon as you quit it, the login process would continue and then drop you into a shell or similar

and even if you don't use pam, and instead launch openrc manually, you're also starting the compositor inside openrc's environment, meaning filtered envs, so things from .profile/.bash_profile/.zshenv won't be picked up, among other things

@WhyNotHugo
Copy link

Passing (filtered) environment variables when starting the runlevel seems like a pretty sensible solution.

I don't think a service manager needs an "import environment" anyway. If services need environment variables, they should be provisioned via their run script.

i do not recommend you run your compositor as a service at all. if you use pam_openrc, it would start during the pam auth sequence, not in a proper session, and as soon as you quit it, the login process would continue and then drop you into a shell or similar

This is a limitation of having root enforce execution of the service manager instead of users executing it themselves. The core of the issue here is involving pam in the execution of the user's services themselves.

@navi-desu
Copy link
Member Author

This is a limitation of having root enforce execution of the service manager instead of users executing it themselves. The core of the issue here is involving pam in the execution of the user's services themselves.

that is true for pam, but even non pam, readiness status for compositors doesn't work well, and there's many moments it can get messes up by being inside openrc's service running enviroment

it could work, but idk if it's worth it considering possible weirdnesses (having to background the compositor's process and all that)

@navi-desu
Copy link
Member Author

i've split most of this PR into 3: #752, #753, and this one

once the other two are reviewed and merged, i'll update this one to use their code instead. you can keep track of all three changes in this branch: https://github.com/OpenRC/openrc/tree/user-services

Adds two functions to librc, rc_set_user() and rc_is_user(). The latter
just queries if the former was called. The former instantiates paths for
locations that make sense for user services:

Script dirs get set to $XDG_CONFIG_HOME/openrc/{init.d,conf.d}, and
variations of the system sysconf dirs with "user.d/" as a suffix.

Runlevel dir is set to $XDG_CONFIG_HOME/openrc/runlevels, and the svcdir
is set to $XDG_RUNTIME_DIR/openrc.

XDG_CONFIG_HOME falls back to $HOME/.config should it be unset.
XDG_RUNTIME_DIR has no fallback, and thus required for user services.
@navi-desu navi-desu force-pushed the user-services branch 2 times, most recently from c09cdd4 to 622f016 Compare October 26, 2024 19:37
@navi-desu
Copy link
Member Author

#752 got merged, #753 is on the debating hell that is my head over "do we want this feature or not"

meanwhile, this PR is just basic user services support, without auto-starting and auto-stopping yet

let's merge this, i'll follow up with the pam module shortly

@navi-desu navi-desu force-pushed the user-services branch 2 times, most recently from fd62d58 to e4dbc44 Compare October 28, 2024 11:05
Add a new function to librc, `rc_usrconfdir()`, whick returns the
location where user-made rc.conf exists.

The user-made configuration is loaded first over the global config, so
it takes priority when resolving variables, thus "overriding" global
configs.
Those variables are required for user scripts to work properly.
Some services might expect to be in home, and may behave unexpectedly
for the user, e.g. any program started via dbus, and this matches
systemd-user behaviour.
Some binaries required to have their `atexit(cleanup)` calls moved to
after argument parsing, since setting user mode also adds an atexit
call, and cleaning up user paths should only happen after the cleanup of
the application is done, thus needs to be set first.
We need to handle --user before calling any functions that interact with
librc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants