Multi Seat Linux Workstation

Current computers are fast enough to handle more than one user at a time. So I started the project to setup my workstation to support two seats, one for me and one for my gf.

Tasks:

  1. Two concurrent Xorg sessions both with one keyboard, one mouse, and two monitors
  2. Separate audio for both seats
  3. Auto mounting of USB storage sticks for the secondary seat. When connected to a specific USB port the usb stick is mounted in the home directory of the logged in user of the second seat.

All this has to work while still keeping root privileges strictly separated. For security reasons I don’t use systemd polkit and other tools that allow normal users to gain root privileges. (Un)Mounting, Shutdown, Printersetup, Hardwaresetup are root tasks, normal users must not be able to do these tasks because it would compromise system security.

A normal user must not be able to shut down the system or see other users USB storages just because she is sitting in front of the local console.

Two Xorg Sessions

The workstation has two graphics cards one nvidia PEG card and an onboard Intel CPU graphics. I had to activate the onboard graphics in BIOS to be able to use it on Linux. The xorg-server-intel driver on Debian Jessie was to old to support the Intel Skylake HD530 graphics, so I upgraded the package “xserver-xorg-video-intel” from jessie-backports (“aptitude -t jessie-backports install xserver-xorg-video-intel”).

Then I configured the Xservers. Xorg can run multiple times with some configuration tweaking. I built two simple Xorg.conf. One for the first seat

# /etc/X11/Xorg.first-desk.conf

Section “Device”

Identifier “Nvidia Graphics”
Driver “nvidia”

EndSection

Section “InputClass”

Identifier “Dell Keyboard”
MatchVendor “DELL”
MatchIsKeyboard “true”
Option “Ignore” “true”

EndSection

Section “InputClass”

Identifier “Logitech Mouse”
MatchVendor “Logitech”
MatchIsPointer “true”
Option “Ignore” “true”

EndSection

And one for the second seat:

# /etc/X11/Xorg.second-desk.conf

Section “Device”

Identifier “Intel Graphics”
Driver “intel”
BusID “PCI:0:2:0”

EndSection

Section “InputClass”

Identifier “TheRest”
Option “Ignore” “true”

EndSection

Section “InputClass”

Identifier “Dell Keyboard”
MatchVendor “DELL”
MatchIsKeyboard “true”
Option “Ignore” “false”

EndSection

Section “InputClass”

Identifier “Logitech Mouse”
MatchVendor “Logitech”
MatchIsPointer “true”
Option “Ignore” “false”

EndSection

Xorg tries take the first graphics card. To force one Xserver to the second card you need the BusID line. You can find this BusID with lspci:

# lspci
00:00.0 Host bridge: Intel Corporation Device 191f (rev 07)
00:01.0 PCI bridge: Intel Corporation Device 1901 (rev 07)
00:02.0 Display controller: Intel Corporation Device 1912 (rev 06)
00:14.0 USB controller: Intel Corporation Device a12f (rev 31)
00:16.0 Communication controller: Intel Corporation Device a13a (rev 31)

The sepration of mouse and keyboard works by blacklisting (“Ignore”) one keyboard and one mouse on the primary Xsession and an inverted blacklist on the secondary seat that blacklists all input devices except this one keyboard and mouse.

Two start two Xorg Xservers I added changed the file /etc/X11/xdm/Xservers to this:

:0 local /usr/bin/X :0 vt7 -config /etc/X11/Xorg.first-desk.conf -novtswitch -nolisten tcp
:1 local /usr/bin/X :1 -sharevts -config /etc/X11/Xorg.second-desk.conf -novtswitch -nolisten tcp

“-sharevts” and “-novtswitch” were the magic settings that allowed to run Xorg concurrently. Without this option the Xservers could only run one at a time by switching between VT7 and VT8 (Ctrl-Alt-F7 / Ctrl-Alt-F8).

Separate Audio

The onboard sound card has 8 channel output for surround sound. ALSA can split this multichannel output to multiple soundcards with this /etc/asound.conf file:

# /etc/asound.conf

pcm_slave.fourchannels {
    pcm "hw:0"
    period_time 0
    period_size 1024
    buffer_size 8192
    channels 4
}

pcm.jack1 {
   type plug
   slave.pcm {
        type dmix
        ipc_key 2381
        ipc_perm 0666
        slave "fourchannels"
        bindings [ 0 1 ]
   }
}

pcm.jack2 {
   type plug
   slave.pcm {
        type dmix
        ipc_key 2381
        ipc_perm 0666
        slave "fourchannels"
        bindings [ 2 3 ]
   }
}

This configuration splits the front from the surround (back) speaker output. Per user you can set the default output to either jack1 or jack2 with this ~/.asoundrc file:

pcm.!default {
    type plug
    slave.pcm "jack2"
}

Currently I hard wired this configuration per user. If me and my GF would change seats frequently I would write a “.asoundrc” file during Xsession startup every time a users logs in on the first or second seat (DISPLAY :0 or :1).

Automounting USB Storage for Second Seat

I used udevd and a small shell script to do the job.

Udevd can start scripts on USB events:

# /etc/udev/rules.d/10-multiseat-usb.rules
#
# filter on SD* (scsi events) of the blockdevice subsystem
# filter on events with the sub device tree (ATTRS) of the second seat's USB Hub idVender==05e3 named "USB2.0 Hub"
# for these events start: /root/user_usb_mounter
# which mounts the device for the logged in user and opens a filebrowser
#
KERNEL=="sd*", SUBSYSTEM=="block", ACTION=="add", ATTRS{idVendor}=="05e3", ATTRS{product}=="USB2.0 Hub", RUN+="/root/user_usb_mounter"

The script /root/user_usb_mounter looks like this:

#!/bin/bash

(
# logfile output
echo "================================" 
date

if [ "$ID_FS_USAGE" != "filesystem" ]; then
    echo "ignoring udev event without FS_USAGE == filesystem"
else
    echo "new files system"

    # look which user is logged in on seat :1
    second_user=`who | grep " :1 " | cut -d " " -f 1`

    if [ "$second_user" == "" ]; then
        echo "No user Session on :1 found, giving up"
    else
        # get userid of logged in user
        muid=`grep -- "^$second_user:" /etc/passwd | cut -d ":" -f 3`
        if [ "0$muid" -le 99 ]; then
            echo "No Userid for User $second_user on :1 found, giving up"
        else
            i=1
            # find an non existant directory mountpoint and create it
            while [ -e /home/$second_user/media/usb$i ]; do 
               i=$(( $i + 1 ))
            done
            mkdir /home/$second_user/media/usb$i
            chown $second_user /home/$second_user/media/usb$i

            #  mount the filesystem in the users home directory
            echo mount -o noatime,nodev,noexec,nosuid,uid=$muid,gid=100 "$DEVNAME" "/home/$second_user/media/usb$i"
            mount -o noatime,nodev,noexec,nosuid,uid=$muid,gid=100 "$DEVNAME" "/home/$second_user/media/usb$i" || exit 

            echo "usbstick mounted to /home/$second_user/media/usb$i"
            echo "starting xfe for $second_user"
    
            # Starting xfe for the user and wait for xfe close. unmount the usb device, inform the user
            (    su "$second_user" -l -c "DISPLAY=:1 xfe /home/$second_user/media/usb$i" 
                 umount "/home/$second_user/media/usb$i" && rmdir "/home/$second_user/media/usb$i" && sync && 
                 su "$second_user" -l -c "DISPLAY=:1 xmessage \"USB Stick is save to remove!\"" && exit
                 su "$second_user" -l -c "DISPLAY=:1 xmessage \"USB Stick umount failed. DANGER!\""
            ) &
        fi
    fi
fi

) >> /tmp/udevtest.log 2>&1

This script checks if the udev event is from a filesystem. Then it checks which user is logged in, gets it’s user ID. Then it mounts the USB device in the users context and home directory. Then it opens a file browser for the user and waits until it’s closed. Then it unmounts the stick and informs the user. This script is not very pretty but it’s a quick and working hack.

Versions: Skylake Intel CPU i5-6500 64bit mode, on ASUS motherboard Z170, Debian 8 (Nov 2017), NVidia GT 640 Nvidia Drivers 375.66, Xorg Intel Drivers 2:2.99.917+git20161206