Linux is often used to build so-called "kiosk" systems; a configuration that will start a fullscreen application right after boot, without any user interaction or login required. This kind of configuration is common for DIY and low-cost devices, but is not as straight-forward as one may assume.
Creating a testing environment
Since testing configurations on a physical device can be cumbersome, this article will use vagrant and virtualbox to create a virtual machine to test the configuration and see it in action. Make sure you have both tools installed, then write the following content into a file named Vagrantfile
:
Vagrant.configure("2") do |config|
config.vm.box = "debian/bookworm64"
config.vm.provider "virtualbox" do |vb|
vb.gui = true
vb.cpus = 2
vb.memory = "2048"
end
end
You can now start your virtual machine with
vagrant up
To interact with the virtual machine once it has been created, run
vagrant ssh
to obtain an SSH session as the vagrant
user. This user is automatically created for you and already has sudo
privileges.
When you are done testing, or want to start over, you can get rid of the virtual machine with
vagrant destroy -f
To get a new clean virtual machine, simply use vagrant up
again.
Starting an app in kiosk mode
The guide assumes you have a linux debian installation without any desktop environment installed (if you're using the vagrant config above, you have exactly that).
Before starting, make sure the packages are up to date
sudo apt update -y && sudo apt upgrade -y
Next install the required packages:
sudo apt install -y xserver-xorg x11-xserver-utils xinit matchbox-window-manager chromium
Here is a brief explanation of what we need each for:
- X server and tools (
xserver-xorg
,x11-xserver-utils
,xinit
): Handles the low-level basics for graphical applications, like displays, inputs and rendering.
- Window manager (
matchbox-window-manager
): Manages windows on top of xorg, including features like size, placement and responding to fullscreen requests. We chose this window manager specifically because it is extremely lightweight and works well for kiosk mode setups.
- Kiosk app (
chromium
): The application we want our system to boot into. Chromium is a simple browser that works well for displaying local or remote websites in kiosk mode, but you could use any other graphical application that has fullscreen support in it's place.
With these dependencies installed, it is time to configure the kiosk mode starting sequence. This requires a user that the system will automatically log in as, preferrably a newly created one for this specific purpose:
sudo useradd -m -s /bin/bash -p '' kioskuser
This creates a new user called kioskuser
. They have a home created because we need to create init scripts there in a moment, use /bin/bash
as their shell because we need .bashrc
during startup, and have an empty password to allow passwordless login at boot.
The initial virtual terminal and login screen is handled by the getty service. In order to automatically log into our kiosk account at boot, we need to override the default parameters for it. Create the file /etc/systemd/system/getty@tty1.service.d/override.conf
with adjusted ExecStart
parameters:
sudo mkdir /etc/systemd/system/getty@tty1.service.d
sudo cat > /etc/systemd/system/getty@tty1.service.d/override.conf<< EOF
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin kioskuser --noclear %I $TERM
EOF
Overriding the getty@tty1
service only applies our automatic login for the first virtual terminal tty1
(not others available through ctrl
+alt
+f2
...f6
, and not remote sessions like SSH). The first blank ExecStart=
line is intentional and important, as it clears the previous contents inherited from the main getty
service unit we are overriding. If it were missing, our arguments would be appended to the original line, not working as intended. If you named your kiosk user differently, make sure to adjust the last line after the --autologin
flag to your custom username.
Now that the kioskuser
is automatically logged in as at boot, we can append a small script to the end of their .bashrc
file to start the xorg
display manager for the tty1
session (and not for subsequent sessions or remote connections):
sudo cat >> /home/kioskuser/.bashrc<< EOF
if [ -z "\$DISPLAY" ] && [ "\$(tty)" = "/dev/tty1" ]; then
startx
fi
EOF
The last task is to start the window manager and kiosk application once the xorg server is ready. We can simply write those commands to a .xinitrc
file, which gets executed once the startx
command completes:
cat > /home/kioskuser/.xinitrc<< EOF
matchbox-window-manager &
chromium --kiosk https://tech-couch.com
EOF
sudo chown kioskuser:kioskuser /home/kioskuser/.xinitrc
Note the ampersand &
after the first command, ensuring the window manager starts in the background and does not block the chromium process.
Using the --kiosk
flag for chromium will automatically start it in fullscreen and disable some UI elements, like tabs and the search bar the top. Instead of a remote URL, you could also provide a filepath to a local HTML file.
All that is left to do now is reboot the machine to see the configuration take effect:
sudo reboot
The virtual machine should now boot directly into the chromium application.
Fine-tuning chromium for kiosk mode
While supplying the --kiosk
flag is a good start, there are other useful flags when running chromium in kiosk mode:
--no-first-run
suppresses the first-run dialog (asking to sign in / sync profiles)
--incognito
ensures browsing history, cookies and site data are not stored, since kiosk applications have to use for these features.
--disable-restore-session-state
prevents chromium from showing a "restore closed tabs" page after unexpected shutdown, like power loss. Since kiosk mode devices are often turned off by unplugging, this feature may interfere with normal operation.
--disable-infobars
hides infobars like the "Chrome is being controlled by automated test software" message that might appear when instrumenting the browser with WebDriver or debugging tools
--disable-pinch
disables pinch-to-zoom functionality for touchscreens.
Depending on your use case, you may find some or all of these additional flags useful for chromium kiosk apps.
Handling power saving and blank screen
If left in default configuration, the xorg display manager will turn off the screen after some time of inactivity to save power. This may me desirable for interactive devices that have a keyboard or touchscreen attached, as any form of input will wake up the system again. For passive devices like screens displaying advertisements or informational displays, this behavior is problematic and needs to be prevented.
You can disable these features entirely by combining three xset
commands. Simply prepend these lines to the beginning of /home/kioskuser/.xinitrc
:
xset s off
xset -dpms
xset s noblank
The first line prevents screen blanking, the second disables dpms (Display Power Management Signaling) and the last one prevents any screen saver activation.
Using a splash screen during boot
The default boot process is very noisy, printing numerous colored lines of logging output to the screen. For many devices running kiosk mode, this boot sequence may concern unsuspecting users, so hiding most of the noise is a good idea.
This can be done by using a plymouth splash screen theme, which first needs to be installed:
sudo apt install -y plymouth plymouth-themes plymouth-x11
Plymouth comes packaged with several themes by default, you can get a list of them with
sudo plymouth-set-default-theme -l
Once you settled on a theme you want, apply it to your environment with
sudo plymouth-set-default-theme -R tribar
The tribar
theme is a good default choice here, as it is lightweight enough to load fast but also clean and unintrusive.
The second part to adjust for a cleaner boot experience is the grub configuration. Open /etc/default/grub
and set GRUB_TIMEOUT=0
(create it if missing). Next, find the line GRUB_CMDLINE_LINUX_DEFAULT
and prepend quiet loglevel=0 splash
to it.
For example, if your grub
file looked like this initially:
GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="net.ifnames=0 biosdevname=0"
GRUB_CMDLINE_LINUX="consoleblank=0"
it should look like this after modification:
GRUB_DEFAULT=0
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT=" quiet loglevel=0 splash net.ifnames=0 biosdevname=0"
GRUB_CMDLINE_LINUX="consoleblank=0"
Finally, run
sudo upgrade-grub
to save your new configuration. The next reboot should be mostly quiet, with a splash screen or animation covering the remaining boot logging noise.
If you want even more control over the boot animation, you could design your own plymouth theme or show a static image.
Configuring virtual terminal
It is common for linux systems to dynamically provide several virtual terminals after boot. A virtual terminal is essentially a login session, just like the one we used to automatically log in. At any point in time, you may press ctrl
+alt
+f2
to switch to tty2
(the second virtual terminal), up to ctrl
+alt
+f6
for the last one.
If you are following along with virtualbox, you need to use the software keyboard from the top menu under input
> Keyboard
> Soft Keyboard ...
to send key combinations to the vm.
Keeping this functionality may or may not be desirable: If end users can send inputs (e.g. a keyboard is connected to the device), they can effectively "disable" the device by switching to a different virtual console (where the kiosk app isn't visible, and just a text-based login appears). Only users familiar with linux systems will recognize this and be able to amend it, while less experienced people have no choice but to restart the device.
On the other hand, devices that cannot be interacted with directly like informational displays may want to keep this feature enabled to allow operators to quickly debug the device in-place, simply by attaching a keyboard to it and switching to another tty to log in and issue commands.
If you want to disable virtual terminals and leave only the session used for the kiosk mode app, you need to find these lines in /etc/systemd/logind.conf
and make sure they are not commented out and have the correct values:
NAutoVTs=1
ReserveVT=0
The first line defines how many total virtual terminals are automatically generated, the second defines a reserved login virtual terminal that is always available (where value 0
disables this feature).
Your changes will take effect after the next reboot.
Preventing users from escaping the kiosk app
Simply starting an app in fullscreen may initially look like everything works as intended, but as soon as users have access to input, they may be able to break out of the kiosk app. This can take many forms, for example alt
+f4
to close the kiosk app, ctrl
+tab
to try and switch to a different program or application-specific shortcuts like ctrl
+w
to close the current tab in chromium (thus closing the browser, since only one tab was open).
The only reliable way to prevent these issues is to unbind all ctrl
and alt
keys (left and right), as this leaves their key combinations inaccessible. You can unbind them using xmodmap
when kioskuser
starts their xorg session by prepending this command to the beginning of /home/kioskuser/.xinitrc
:
xmodmap -e "keycode 37 = NoSymbol" \
-e "keycode 64 = NoSymbol" \
-e "keycode 105 = NoSymbol" \
-e "keycode 108 = NoSymbol"
This solution has the added benefit that it only affects the kiosk app session on tty1
, other sessions (e.g. over SSH) and other users remain unaffected.
After a reboot, feel free to try all key combinations to ensure you cannot escape the kiosk environment with them anymore.
If you are following along with virtualbox, you need to use the software keyboard from the top menu under input
> Keyboard
> Soft Keyboard ...
to send key combinations to the vm.
Considerations for touchscreen devices
Before adding touchscreen features to this setup, take a moment to consider if debian is the right platform for your needs. Handheld-optimized distributions like android will be significantly easier to set up for touch devices, and you are less likely to find edge-case issues or hardware incompatibilities with them.
The primary need of a touchscreen device is a soft keyboard (an on-screen software keyboard) to allow users to input keystrokes without a physical keyboard connected. There are many options here, from lightweight choices like matchbox-keyboard
(a good choice for the matchbox-window-manager
) to more complex and feature-rich alternatives like florence
or onboard
. The choice is mainly up to your device's needs and personal preference, but be warned that setting up dynamic focus (automatically show/hide the keyboard when text input is selected) may be tricky on some combinations.
Since most touch devices do not show a cursor to the user, you likely want to hide the one on your touch device as well. An easy solution is to use unclutter
with an idle timeout of 0 seconds, to hide the cursor most of the time. This setup may cause a flickering cursor on mouse move for some touchscreens, in which case you need to use a more complex solution like creating and installing a cursor theme with an invisible cursor icon.
Lastly, your kiosk application may behave unexpectedly with touch devices. The chromium browser for example, includes support for some touch gestures, like navigating with swiping motions or zooming with a pinch gesture. These can be easily disabled with flags --overscroll-history-navigation=0
and --disable-pinch
, respectively. Spend some time checking if and what touch gestures your application supports, and consider how that may negatively impact the user experience.
Device maintenance and security
After setting up your device, you may think about how to maintain it in the future. Running an SSH server on the device to remotely manage it may seem like a good idea at first, but be careful to either disable SSH login for the kioskuser
account or limit logins to key-only authentication, as otherwise you provide remote access to anything in the device's network through the passwordless kiosk application user.
On the topic of security, you should make it a point to treat all your deployed/installed devices as hostile. Even if no remote access is possible over SSH, an attacker could simply plug the storage disk into one of their machines and freely read/change any files on the system. Do not store credentials on the device (like S3 storage bucket logins or FTP credentials), not even hardcoded into a compiled binary (strings can be stripped out of executables easily with the strings
command or debugging software).
If your device needs authenticated access to remote infrastructure, for example to forward orders or reports, consider deploying a purpose-built REST API with limited access for the devices, and provide different API tokens for each device to easily lock specific ones out of the service should they start behaving undesirably or their token becomes compromised. Audit logging and rigid monitoring/alerting are a valuable addition of such an API in production environments.