Creating a kiosk with Linux and X11: 2011 edition
Back around 2006 our public library was in need of a cheap way for patrons to browse its web-based INNOPAC catalog. Thin clients running Windows CE had been purchased for this purpose, but they turned out to be buggy and limited. I was tasked with finding a solution to the problem “on the cheap”, and being a fairly new Linux fanatic at the time, I figured I’d see what I could do using free software. This led to my first kiosk project.
Since then, I’ve refined my approach time and again, deploying kiosks throughout my organization just about anywhere a web-browser kiosk can be put to use. The original library system has been completely rebuilt with newer hardware and software, but is fundamentally the same system I set up five years ago.
I often see people asking about how to set up a kiosk system with Linux, and like me they usually start out going about it the wrong way; so I thought I’d write this tutorial based on my years of experience to help those getting started.
Most people start working on a kiosk with the idea of taking some standard Linux desktop (like Unity or KDE) and trying to “lock it down” so users can only run a browser. I tried this too, originally (at the time, KDE 3 had a kiosk tool that let you set policies for all its 18 bajillion components); but I quickly found that locking down a fully-featured desktop environment was a pedantic, exhausting task that never ended satisfactorily. It occurred to me that I might have more luck building an environment from scratch with only the components required.
That isn’t nearly as hard as you’d think. To understand how it works, let’s look at how a typical X11 desktop system functions:
- When you boot the system, it launches your X11 server (xorg) and the display manager, which is what gives you your graphical login screen.
- In addition to letting you log in, the display manager also lets you choose a session (such as Gnome, openbox, XFCE, etc depending on what’s installed). Each session is just a script or program that can run in X11.
- When you log in, the display manager run the session script/program under your account. The session script launches the programs that comprise your desktop and provides you with everything you see.
- When you log out, your session script finishes execution and exits. Control of the display is returned to the display manager.
The key here is understanding that the session script (or program) is nothing magical. It’s just an executable that the display manager runs when you log in, and when it finishes you log out. In fact, there is a very simple mechanism to override the X session and create our own customized version. When a user logs in, if the ~/.xsession or ~/.xinitrc file is present and executable it will (usually, depending on the display manager) be executed instead of the default session.
Now, I’ve said that there’s nothing magical about the session script; but those who haven’t done much tinkering with X11 desktops might be surprised to find that many of the behaviors you take for granted on a graphical desktop don’t “just happen” — they require software to make them happen. For instance, you might assume that program windows are automatically movable about the screen, are re-sizable, or have title-bars across the top with (at least) maximize, minimize, and close buttons. None of this is provided by X11, though; it’s provided by a program called the window manager. You might also assume that graphical environments all have menus, desktop icons, or panels; again, these are provided by other programs which must be run either by the session script or one of the programs it starts.
Choosing the right components to launch in a session script can be subtly tricky depending on the software you need to run, the behavior you expect from it, and the need for tight security on the system. Fortunately, when it comes to desktop components the Linux world offers a lot of options, and with some persistence and a willingness to tinker you can nearly always get what you need. At the end, we’ll go through some “crash test” questions to see if your setup is really bullet-proof and ready for the road.
But first, let’s walk through setting up an actual kiosk.
In this tutorial, I’ll walk through how I set up a kiosk for use at my workplace, and why I choose the different components that I do.
Choose and install the OS
The first task is to get a basic OS install. You can do this with about any distribution of Linux you feel comfortable with, but I’d recommend taking the following aspects into consideration:
- You want big repositories with a lot of selection. Since we’re going to be dealing with some less-than-ubiquitous software in constructing our custom environment, make sure you use a distro with a lot of these uncommon packages available.
- Don’t use a rolling release or a distro that updates every few months. The idea is to put out a kiosk and not touch it for a few years if you don’t need to. Use something with a long release cycle, or a long-term support release.
- Use a distribution that can be installed with a minimal command-line installation and built up from there.
- Lightweight is good, since you might want to use this on old computers or thin clients.
Based on these criteria, I’ve found Debian Stable to be a prime choice over the years. Ubuntu can also work well if you stick to LTS releases and use the minimal install media. Centos might be a third choice, though it doesn’t have as much software readily available.
Once you’ve picked one, install a basic, command-line installation. The Debian installation manual can be found here.
Install the software
Once you have the basic install, we need to get a few software components on there:
- xorg is vital for displaying video
- gdm (version 2) will be the display manager
- matchbox-window-manager will be our window manager
- chromium-browser will be our sample kiosk application
- emacs for editing text, though you can install your favorite CLI text editor instead
- rsync will be part of our security lock-down later
- openssh-server is useful for doing remote administration
Use aptitude on Debian to find and install these packages. Dependencies will, of course, be handled automatically. You might want to add the “–without-recommends” switch when installing gdm, so that you don’t pull in a lot of unnecessary GNOME packages. Also, if you prefer to do your administration parts in a GUI, you can of course install a minimal desktop like LXDE or just a simple window manager like jwm. Be aware that any other environment you install is going to show up on your login screen as a session option, so depending on the deployment situation that might be a problem.
We’re using Chromium here as an example kiosk application, though if you have a custom application or prefer a different web browser substitute that instead. I originally used Firefox for these, but unfortunately Firefox has become increasingly unsuited for kiosk work since version 2 (I actually ended up writing my own browser in python, but I’m assuming you’re not that deep into this yet).
I chose matchbox-window-manager here because it’s simple and stays out of the way. In the past, I’ve used flwm, ion, openbox, or xfwm. I ended up switching to matchbox because it does pretty much what you want on a kiosk without any configuration.
If you’re logged in as root at this point, make sure you’ve made a second account for administration purposes — you really don’t want to be ssh’ing around as root, it’s bad practice. You also need to make the account which the kiosk will run under; I usually just call this “kiosk”, though feel free to call it whatever seems natural to you.
In Debian, the “adduser” command will step you through setting up a new user account. Try “adduser kiosk” and follow the prompts.
Configuring the session script
Time for the most important part: writing the session script. First, run these commands as root:
cp -r /home/kiosk /opt/ cd /opt/kiosk/ chmod -R a+r . touch .xinitrc chmod a+x .xinitrc
What these commands do is copy the kiosk user’s home directory to /opt, move there, make the directory world-readable, and then create an empty but executable .xinitrc file (see note at the end of the section about .xinitrc vs .xsession). You want /opt/kiosk to be readable by the kiosk user, but not writable.
Now open the .xinitrc file in a text editor and copy in this text:
xset s off xset -dpms matchbox-window-manager & while true; do rsync -qr --delete --exclude='.Xauthority' /opt/kiosk/ $HOME/ chromium-browser --app=http://myserver/mywebapp done
This is our session script; it’s what will be run when the kiosk user is logged in, so let’s go through it one line at a time.
The “xset” lines take care of an important detail: disabling screen blanking and monitor power saving. You might not want this, but most users approaching a kiosk with a black screen tend to assume it’s out of order.
Next, we launch our window manager, matchbox-window-manager. Note the ampersand here: without it, matchbox would be run and nothing else would happen, because the script would be waiting on matchbox to end before running the next commands. You do need one part of the script to run indefinitely, because once the script ends the user is logged out; but you want only one part of the script to do this. The ampersand forks matchbox’s execution to the background and continues down the script.
The “while true; do” line starts an infinite loop, which is later closed by the “done” line. There’s really no way out of this script other than to kill the whole display or reboot the system, and that’s what we want.
The “rsync” command bears some explanation. Rsync is a special copy command that, by default, only copies files that are different between the source and destination. The switches and arguments do the following:
- q makes the output quiet, so we don’t bother writing to a log file that is probably going to be overwritten anyway
- r makes the copy recurse into subdirectories (without it, no directories will be copied)
- delete means that any files in the destination that aren’t in the source will be deleted. This is will wipe out any files the last user may have downloaded or created, either inadvertently or maliciously.
- exclude means it won’t touch the listed file. “.Xauthority” is a file created automatically by xorg, and overwriting or deleting it during a running session could potentially crash the session.
- /opt/kiosk is our source for copying. The “/” on the end is important, don’t omit it!
- $HOME is an environment variable that points to the logged-in user’s home directory. Being the last argument, it’s the destination for our copying. Again, the “/” on the end is critical to make sure things get copied to the right place.
Every time the rsync runs, the kiosk user will get a completely fresh home directory; this will reset anything a previous user might have tinkered with. Since it’s in an infinite loop with the actual kiosk application, resetting everything is as simple as closing the application.
Finally, we run our kiosk application. I’m using chromium-browser with the “–app” switch to put it in kiosk mode (replace the URL with one of your choosing). You can, of course, run any application here, but note that it’s critical the application not fork into the background when run. Some applications (OpenOffice, for one) fork when run, and return control to the command line. If your application does this, you’ll end up with a few hundred copies before the system runs out of memory and dies. You’ll have to work out how to “block” the session some other way if your application is like this.
Once this file is saved, copy it to /home/kiosk. Reboot the system, and log in to GDM as the kiosk user. Ta-dah! You should be looking at a full-screen chromium with whatever URL you specified.
NOTE: as one reader discovered, apparently newer versions of GDM in Ubuntu don’t recognize “~/.xinitrc”, but do recognize “~/.xsession”. If you’re having trouble with GDM recognizing your session script, rename it to “.xsession”.
Already this is a pretty secure setup that will keep the vast majority of people from doing anything destructive, but there are a few more things we might want to do depending on where you’re deploying the kiosk and what you think people might do (for space reasons, I can’t detail all these, but I’ll tell you where to find information):
- Lock GRUB: By default, GRUB allows any user to edit boot options from the GRUB prompt, giving knowledgeable users a potential avenue to gain root access. Section 15 of the GRUB manual (type “info grub” at the console for the manual) describes how to password-protect boot options and editing.
- Lock the BIOS: Most computers allow a BIOS password to be set for editing BIOS. You probably want to set this, and disable any sort of boot-device menu features. Of course, you also need to physically secure the cpu if you’re worried about people booting to other devices.
- Remove sudo/su access: In the event that a user finds a way to get a terminal, you don’t want them being able to run su or sudo (even if they don’t know the password, they can spend a long time guessing on a public kiosk). Check out the files /etc/pam.d/su and /etc/pam.d/sudo on Debian, and read carefully to learn how to disable these commands for your public user. On Ubuntu, you might find apparmor to be useful here as well.
- Be careful with the URL bar: It’s easy to forget that most web browsers, including chromium, can browse the filesystem just as well as they can browse the web. This can potentially allow a user to inspect system files or even run commands. In our example, the “–app” switch makes the URL bar inaccessible, but there may be ways around this. Make sure you check for things like this and take measures to limit access to the filesystem.
- Disable TTYs: By default, most Linux systems will spawn six TTY terminals in addition to the X11 display, which can be accessed with the keys ctrl-alt-F1 through ctrl-alt-F6. You probably want to disable these for the same reason you want to disable su and sudo — no sense in giving someone the opportunity to try root passwords. In Debian, edit (as root) the /etc/inittab file and comment out these lines:
1:2345:respawn:/sbin/getty 38400 tty1 2:23:respawn:/sbin/getty 38400 tty2 3:23:respawn:/sbin/getty 38400 tty3 4:23:respawn:/sbin/getty 38400 tty4 5:23:respawn:/sbin/getty 38400 tty5 6:23:respawn:/sbin/getty 38400 tty6
- Prevent root login to GDM: Again, you don’t want to have people testing root passwords at the GDM login prompt. Edit /etc/gdm/gdm.conf and under the “[security]” section add “AllowRoot=False”.
Not all of these are strictly necessary, but as the voice of experience let me advise you not to underestimate the public’s ability to cause mischief on an unsecured computer!
At this point your system is workable and secure, but depending on the situation there are some optional features we might want to add.
If you want the system to be a “turnkey” style system that doesn’t require login, configure GDM for auto-login. You just need to edit /etc/gdm/gdm.conf, and add the following under the “[daemon]” section:
[daemon] AutomaticLoginEnable=true AutomaticLogin=kiosk
Reset on Timeout
So your kiosk user is looking up information about an embarrassing rash, then gets a phone call and has to step away. You don’t want the next person walking up to see information about embarrassing rashes. You also want to protect your users by removing web history between users when possible.
In my own kiosks, I built a timer into the application to wipe the history and return to the home page after so many minutes of inactivity. With the chromium-browser kiosk, you don’t have that option. But you can get it with a little help from xautolock.
Xautolock is a utility that monitors the session for inactivity, and locks the screen after so many minutes. It doesn’t actually have to lock the screen, though; you can tell it to run any command you want after the inactivity period has timed out.
Looking at our session script, all we need to do to reset the whole thing is close chromium. So the solution is simple:
- Create a script to kill chromium; probably it just needs to contain “pkill chromium-browser”. Save it to /usr/local/bin/kill_chromium.sh.
- Install xautolock and add this line to your session script, beforethe loop starts:
xautolock -secure -time 15 -locker /usr/local/bin/kill_chromium.sh
This runs xautolock in secure mode (so it can’t be turned off), sets the inactivity timeout to 15 minutes, and runs your chromium-killing script when the timeout hits.
Chances are, you don’t want to mess with keeping these systems up-to-date manually, and you certainly can’t expect users to launch an updater for you! So, this is the simple way to set up automatic updates on your Debian system (I wouldn’t recommend doing auto-updates on a distro that’s prone to major changes, or you might have a broken kiosk on your hands!).
- First, install cron-apt from the repositories.
- By default, cron-apt will update the package lists daily, but won’t upgrade the system. So:
- Go to /etc/cron-apt/action.d
- create a file called “9-dist-upgrade”
- Put in it this line: “dist-upgrade -y -V -o Dpkg::Options::=–force-confold”
- Save the file and exit.
If you have the capability to relay mail from your systems, you can configure cron-apt to email you when it upgrades or has a problem upgrading, but I’ll leave that for your homework.
If you want your users to be able to print, you’ll need to install cups. Mercifully, cups comes with its own web-based configuration utility, so if you’re a little tired of hacking config files, rejoice! After installing cups, point the web browser to “http://localhost:631” to configure printing.
Testing the system
Now that your system is set up, test it extensively. If possible, have some other people try to mess it up. Here is a list of some non-obvious things to try:
- Window Manager
- Does the WM offer keyboard shortcuts or a menu? Are you able to use them to launch other applications, or get to a terminal?
- Can you minimize windows? Once you do, is it obvious how to restore them?
- Is it possible to log out with your setup? What happens?
- What happens when dialogs pop up? Or different file types are downloaded? Is it obvious how to close windows that might need to be closed, or find them if they lose focus?
- What happens when the user hits the “magic sysrq” combinations? Try alt-sysrq-K, for example.
- Can your kiosk application be put in an broken or messed up state with no obvious way to restore it?
- Can your application be used to launch system utilities or browse system files?
- Does your application have problems if closed improperly?
- Is there an obvious way for users to “reset” the program if they’re stuck, or if they want to delete their history?
- Is it possible to browse unwanted URLs from the browser? Do you need to apply a proxy filter or firewall?
- Watch carefully at boot; is any sensitive information exposed on the screen? Are there places where a user could stop the boot process and get an interactive prompt?
- Scan your system from another system on its network using nmap. What ports are open? Do you need to firewall those ports, or remove the services listening on those ports?
- Are login prompts exposed on the system, so that a user could try passwords for root?
- Is there a way to secure your case? Mouse? Keyboard?
- Does the keyboard or mouse have “special keys” like multimedia or Internet buttons, scroll wheel buttons, etc. What do these do when pressed?
- Are USB ports exposed on any of the peripherals? Have you secured the system against booting from USB?
Obviously, your need for security and bullet-proof operation may vary depending on where and how you intend to deploy this system; but spend some time trying to think like a hoodlum with Unix experience, and see what you can do to secure the system against tampering.
Just the beginning
This is just the start of what you can do with Linux and kiosk setups. Now that you understand how to use the .xinitrc, the sky’s the limit! If you’ve created Linux kiosks, or if you’re unsure how to solve a particular problem you’re having, leave a comment and tell me all about it.
2012 Update: Some additional resources
This article has proven to be somewhat popular as a resource for setting up Kiosk systems on Linux, so I’m including some links to additional resources that I’ve posted on this blog since I wrote this article:
- WCGBrowser — this is the browser I ended up writing to replace Firefox on my kiosks. It’s built in Python, QT, and WebKit, and it’s fully configurable through a plain text YAML config file. I released the code since writing this article, and continue to develop it into a general-purpose solution for kiosks. It’s Free under the GPL v3, and the code is on my github page.
- KiLauncher — a full-screen launcher menu, also built with Python and QT and configurable through a YAML config file. It’s also theme-able using QT stylesheets ( a subset of CSS). Also Free under the GPL v3, and the code is on my github page.