====== Multi-user auto-login on OSX ====== ===== Motivation ===== For a large software product, we use separate user-accounts for automatic builds. All build accounts are separate and non-administrator accounts. The last step of the build-process packages the application and arranges application icons. This requires a Finder Process for the user running the build-process, which in turn requires the user to be logged-in. Also for running select unit-tests, application windows are needed, which also depends on a local session. ===== General Setup ===== The process are launched by a cron-job which launches the build/test processes inside a screen-session. This allows to log the output (via screen) and also to attach to a running process if needed. All build-users are password-less (empty passphrase) which facilitates fast-user switching and have ssh-key for remote access (by default ssh does not allow login with an empty password, make sure to keep this config) ===== The Problem ===== OSX only allows to auto-login a single user. There is no standard way to automatically log-in multiple users at startup. Every time the machine boots, all build-users need to be logged in. With a password-less setup, it's easy enough to simply VNC into the machine (using a dedicated user-account/passphrase) and then fast-user-switch cycle through all users, but it's not always convenient to do so. ===== The Solution ===== A combination of various mechanisms is needed. The general way to switch users from the commanline is easy enough: /System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -switchToUserID The crux with this command is, that it requires a session in the first place. Calling this command directly from launchd ''~/Library/LaunchAgents/'' does not work. ''CGSession'' sends a signal to the //currently active// session. In order to chain multiple automatic logins, the command needs be executes in the same Mach bootstrap namespace as the loginwindow which spawned the current session. To put this all together, the solution is as follows: - Autologin first user (using standard OSX mechanism: System Preferences > Accounts > Login Options) - Login Items (Prefs > Accounts) for this user launches a script to switch user. - Login Items for the newly logged in user switches to the next.. (chain) which leaves us with some small further complications: * Login Items cannot directly launch a shell script. * Setting the Mach bootstrap environment (even for a owned process) requires admin privileges The first is easily remedied. Using the `Script Editor` allows one to compile an AppleScript into an .app bundle. This applescript is intentionally left simple as to re-use it for various accounts: ''autologin.app'' - AppleScript do shell script "$HOME/bin/autologin.sh" \\ To work around the admin requirement, all local users are allowed to run a script via sudo: ''sudoers'' -- edit via `sudo visudo` %localaccounts ALL=(root) NOPASSWD: /usr/local/bin/switchuser.sh \\ The script that is used for switching users, it takes 2 arguments: //current-username// and the //UID// to switch to. Since this script is invoked via sudo, it is not trivial to find the calling user's name so this needs to be passed as argument. ''/usr/local/bin/switchuser.sh'' #!/bin/bash LOGINPID=$(ps auxwwww \ | grep -v grep \ | fgrep /System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow \ | egrep "^$1" \ | awk '//{print $2;}') launchctl bsexec $LOGINPID \ "/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession" \ -switchToUserID $2 \\ and finally a small script to tie it all together: ''$HOME/bin/autologin.sh'' #!/bin/sh sudo /usr/local/bin/switchuser.sh `whoami` `id -u nextbuilduser` \\ This last script is the only one that differs for every user. ''nextbuilduser'' needs to be adjusted to chain logins. All user-accounts can share ''autostart.app'' as Login Item. . The actual ''/usr/local/bin/switchuser.sh'' is a bit more elaborate to gracefully handle errors and prevent abuse with bogus parameters. There is a ''if test -z "$LOGINPID"; then'' case to catch potential PID lookup errors, and also contains a ''if test -z "$1" -o -z "$2"; then'' to verify that all parameters are given, as well as two ''`id`'' checks to see if the script is running as ''root'' and the target user exists. These are not essential and have been left out for brevity. To sum up, the complete flow is: - System auto-logs in first user - Login-Item runs ''$HOME/bin/autologin.app'' after logon. - the ''$HOME/bin/autologin.sh'' shell script is called from the .app - the shell script determines current user ''`whoami`'' and the UID of the next user to log in ''`id -u name`''. - the shell script launches ''/usr/local/bin/switchuser.sh'' with admin privileges. Due to //sudoers// setup, no passphrase is needed for local-users to run this command. - ''switchuser.sh'' triggers the actual user-switch. - Go to Step 2 While this is all pretty straight forward, it's a complex setup for a simple task. Note: if using empty-passwords is not an option, there are means to inject plaintext passwords, the switchuser.sh can in turn call an applescript (the password would become a 3rd parameter). e.g. osascript << EOF tell application "System Events" keystroke "yourpasswordhere" & return end tell EOF {{tag>OSX development article}}