Skip to main content
Topic: runit-user-spawn (Read 1895 times) previous topic - next topic
0 Members and 2 Guests are viewing this topic.

runit-user-spawn

Following the announcement of dinit-user-spawn and related issues I wondered if something similar existed for runit which I use.

First off I could not find any simple way to run a script at user login/logout. I could use pam.d/system-login to add a session optional pam_exec, but that would not be easily controlled.

I noticed that directories /run/user/uid depend on login/logout so scanning /run/user seems like a way to tell who is logged in.

I wrote this flaky bash script to test the idea,  It seems to work when installed as /etc/runit/sv/users/run and enabled. It uses a polling loop which is rather awful. I could possibly use inotifywatch to monitor /run/user for changes which would eliminate the polling loop.

Personally I don't need such a system as I use openbox autostart to run services needed by the desktop. If I felt restarts were required I suppose I could start a runsvdir directly in autostart. I don't think I would need such when lgging in on a console or using ssh.

Anyhow here is the code that works inelegantly

Code: [Select]
#!/bin/bash

exec >> /tmp/users-run.log 2>&1
echo "#############################################################"
declare -A UD

alive(){
ps -p "$1" >/dev/null 2>&1
}

do_kill(){
echo "do_kill $@"
kill "$1" "$2"
}

cleanup(){
echo "##### cleanup on EXIT"
for k in ${!UD[@]}; do
pid="${UD["$k"]}"
if alive "$pid"; then
do_kill -HUP "$pid"
if alive "$pid"; then
sleep 1
do_kill -TERM "$pid"
sleep 1
if alive "$pid"; then
echo "!!!!! cannot kill runsvdir ($pid)" 1>&2
continue
fi
fi
fi
done
}
trap "cleanup" EXIT

while true; do
sleep 1
#first check deleted
for k in ${!UD[@]}; do
if [ ! -d "/run/user/$k/" ]; then
echo "detected logout of $k"
pid="${UD["$k"]}"
if alive "$pid"; then
user="$(id -nu $k)"
do_kill -HUP "$pid"
sleep 1
if alive "$pid"; then
do_kill -TERM "$pid"
sleep 1
if alive "$pid"; then
echo "!!!!! cannot kill runsvdir ($pid,$user)" 1>&2
continue
fi
fi
fi
unset UD[$k]
fi
done
#now additions
for d in $(ls -d /run/user/*/); do
k="$(basename $d)"
pid="${UD["$k"]}"
if [ -z "$pid" ]; then # || ! alive "$pid"; then
user="$(id -nu $k)"
home="$(getent passwd "$user" | cut -d: -f6)"
svdir="$home/${USER_RUNIT:-.config/runit}/service"
if [ -d "$svdir" ]; then
OUSER="$USER"
OHOME="$HOME"
export USER="$user"
export HOME="$home"

groups="$(id -Gn "$USER" | tr ' ' ':')"

chpst -u "$USER:$groups" -C "$HOME" runsvdir -P "$svdir" ................................................................ &
pid="$!"
UD["$k"]="$pid"
echo "runsvdir ($pid,$user)"
OUSER="$USER"
OHOME="$HOME"
fi
fi
done
done

Re: runit-user-spawn

Reply #1
I noticed that directories /run/user/uid depend on login/logout so scanning /run/user seems like a way to tell who is logged in.

It's handled by elogind (or turnstile if one manually enables rundir management (needs patched elogind)), so users explicitly -Rdd'd elogind (and make its equivalent whatever they do) won't be able to use the script that way. Don't think Artix supports it though.
now only the dinit guy in artix

Re: runit-user-spawn

Reply #2
Quote
Following the announcement of dinit-user-spawn and related issues I wondered if something similar existed for runit which I use.

Turnstile has settings to use runit as it user services manager, which are maintained by the Void Linux team. Artix doesn't package any prebuilt service definitions for runit, though, so using it is more work than just pulling dinit-base + *-dinit-user regardless of your PID 1.

The way it supports multiple init choices is pretty interesting: instead of directly spawning an "init" itself, it spawns a tiny script/program that receives some context in its positional parameters, then exec's the real thing.

The header comments explains what these are, and how Turnstile calls them. (I picked an older commit because a while ago the script got hairier due to integration with GUI sessions).

Since dinit-user-spawn is smaller in scope, I think it can get away with fewer arguments. Making dinit-user-spawn "init-agnostic" would also mirror what happened to Turnstile itself (it used to be named dinit-userservd and could only spawn dinit instances).



I tried writing an s6-rc backend for it a few months ago, but its aggressive minimalism made it harder to create something that wouldn't require additional setup by the user/distro.

Quote
I could use pam.d/system-login to add a session optional pam_exec, but that would not be easily controlled.
I noticed that directories /run/user/uid depend on login/logout so scanning /run/user seems like a way to tell who is logged in.

As konimex said, you're just shifting login detection onto elogind and/or turnstile, which use PAM.

At least inotify would be less ugly than polling loops, which apparently both *-user-spawn solutions use. dinit-user-spawn only polls for /run/user, and only during startup.

Re: runit-user-spawn

Reply #3
Good evening,

On the artix-dev IRC today, we actually discussed this. Later, i'll add a config option to dinit-user-spawn to spawn an arbitrary binary (ie. that could be runit), instead of what is currently hard-coded to dinit. The program does not actually rely on dinit at all, it just spawns an arbitrary binary which in this case happens to be dinit. If this goes well, then dinit-user-spawn will transform into init-user-spawn, and just have a config option which allows user to spawn whatever init they want, rather than the hardcoded string of "/usr/bin/dinit".

At least inotify would be less ugly than polling loops, which apparently both *-user-spawn solutions use. dinit-user-spawn only polls for /run/user, during startup.

dinit-user-spawn checks /run/user during startup, and uses the inotify API, upon /run/user/, to see any subsequent user logins or logouts (by directory creation or deletions). The latter (inotify) is the main way it actually launches dinit for user logins and logouts, but I also added the functionality that it checks if any users are already logged in, incase dinit-user-spawn is started later (and not before any user logins), which is very helpful for development purposes. You can see the use of the inotify api within main.cpp at line 283 and beyond (line numbers accurate to commit d54ded9). You can also see the comment re-iterating what i mentioned above about checking /run/user on startup on line 258.

To note, dinit-user-spawn doesn't poll anything beyond yielding until the directory /run/user exists. The check for existing folders in /run/user is one-shot and beyond that inotify is used.

Since dinit-user-spawn is smaller in scope, I think it can get away with fewer arguments. Making dinit-user-spawn "init-agnostic" would also mirror what happened to Turnstile itself (it used to be named dinit-userservd and could only spawn dinit instances).

To a degree, but only for launching init systems as user processes where applicable. The scope creep of expanding the goal to reimplement logind (like turnstile), is off the books in my eyes. Generally, I'll just add configuration options where it is applicable and easy, such as simply just changing the hard-coded /usr/bin/dinit string to be something you can modify in the config.


Re: runit-user-spawn

Reply #4
I think one only needs to perform a runsvdir on a service directory with an appropriate environment cgroup user etc etc.

A ~/.config/runit folder can hold per-user ~/.config/runit/sv/<service>/run and ~/.config/runit/service/ which holds the enabled user service links.

Those runsvdir processes need to live somewhere so I thought that a single system runit service could handle the startups of all the user runsvdirs.

I have nothing against dinit, but runit is my preferred init and as the above script shows all that's really needed is a mechanism to observe session events and a bit more control over the child processes (all runsvdir) created by a system run script.


Re: runit-user-spawn

Reply #5
I see, I don't know how runit works myself, I'll still add an interchangeable binary option if just for the sake that people with dinit installed in non-standard locations can run that, and people can make their own solutions (like you mentioned) for runit.

Re: runit-user-spawn

Reply #6
Good evening,

On the artix-dev IRC today, we actually discussed this. Later, i'll add a config option to dinit-user-spawn to spawn an arbitrary binary (ie. that could be runit), instead of what is currently hard-coded to dinit. The program does not actually rely on dinit at all, it just spawns an arbitrary binary which in this case happens to be dinit. If this goes well, then dinit-user-spawn will transform into init-user-spawn, and just have a config option which allows user to spawn whatever init they want, rather than the hardcoded string of "/usr/bin/dinit".

From what I know of other inits, having a directory with wrapper scripts makes more sense. dinit does a lot of the "magic" of using different paths, avoiding root-only behaviors etc. automatically, while runit and s6 need to be explicitly told to do that kind of thing.

Even for dinit, a wrapper would help; for instance, most of functionality equivalent to the minimal_environment_handling = false setting is supplied by having the wrapper script run . /etc/profile before exec dinit — it's what sets most of the environment anyway, and it's safer (less privileged, and immune to variables with embedded newlines and equals signs) than parsing the output of su -c env.



Speaking of minimal_environment_handling, I think reading LOGNAME, HOME and SHELL from the passwd struct should also be part of it.

Quote
To note, dinit-user-spawn doesn't poll anything beyond yielding until the directory /run/user exists. The check for existing folders in /run/user is one-shot and beyond that inotify is used.

My apologies for posting before checking, and not being clear enough with my edit.

Quote
To a degree, but only for launching init systems as user processes where applicable. The scope creep of expanding the goal to reimplement logind (like turnstile), is off the books in my eyes.

I'm not advocating for a similar scope extension; just for more flexible software.

Quote
Generally, I'll just add configuration options where it is applicable and easy, such as simply just changing the hard-coded /usr/bin/dinit string to be something you can modify in the config.

Since the binary is no longer necessarily dinit, the system-wide process search here should be replaced with a search in the uid_pid_map. The dinit_args setting should probably be renamed too.

 

Re: runit-user-spawn

Reply #7
Good evening, thank you for the response.

Just to preface, after thinking about it a bit more after my original post and replabrobin's subsequent post, I have decided it would be best to leave dinit-user-spawn as dinit specific and then let others fork dinit-user-spawn if they wish to make it specific to another init system like runit. This seems like the cleanest solution to me as each init seems to have their own specific unique behaviours that I don't particularly want to accommodate all within a single program, and forks would be able to do better anyway.

My apologies for posting before checking, and not being clear enough with my edit.
No problem

Since the binary is no longer necessarily dinit, the system-wide process search here should be replaced with a search in the uid_pid_map. The dinit_args setting should probably be renamed too.

Although I am no longer going to generalise the dinit-user-spawn program (and rather leave it to forks to fork if they want to), thank you for bringing this up, as the systemwide search should instead try and check for the binary specified in the user configuration, which is not currently the behaviour in the dev branch, and so i'll fix that in the dev branch.