Building a simple lightweight web kiosk system with Arch GNU/Linux

Update 29/03/2014 : This tutorial is currently outdated and may not work as intended. I made it before Arch switched to the systemd init system.

Optimized for maximum boot speed and read-only filesystem operation (especially for usb drives and other flash memory cards).

DISCLAIMER : As always, use this tutorial at your own risk!

Hardware used for this howto :
Mini-ITX motherboard with Pentium-M 1.5GHz (centrino)
512M DDR ram
Integrated graphics, sound and ethernet.
8G Compact Flash card with IDE-CF adapter.


Go and download the official netinstall iso image at
As root, copy the iso to an empty USB drive (it even fits on an old 512M drive)

# dd if=archlinux*netinstall*.iso of=/dev/sdX bs=1M
# sync

Usual warning, replace /dev/sdX with your actual drive, ALL DATA WILL BE ERASED on the drive!
Now boot the system with the usb drive (32 or 64 bits, the choice is yours).
When booted, you are presented with a short message and a root prompt, let’s begin installation.
Setup your keymap for a more comfortable usage (here for french keymap)

# loadkeys fr

If you need to see the short installation message again

# more /etc/motd

Fire up (wired) networking (here for a dhcp client on eth0)

# dhcpcd eth0

If you don’t have a dhcp server, setup the interface manually with ifconfig and write your DNS servers in /etc/resolv.conf

# ifconfig eth0 <ipaddr> <netmask>

Now it’s even better if you can log on the system via SSH and copy/paste the rest of the commands from this howto!
Set up a password for root (ssh logins are not possible without password by default)

# passwd

Startup SSH daemon

# rc.d start sshd

And you should be able to connect remotely on this machine from another.
TIP : to see your ip address

# ip addr show eth0

Partition your target installation media (here it’s /dev/sda for me).
Just for the sake of changing a bit we will use cfdisk instead of fdisk.

# cfdisk /dev/sda

Create 2 partitions :
/dev/sda1 : for the system (bootable)
/dev/sda2 : swap partition (should be at least the size of the system’s RAM)
Format the filesystem (here for a read-only system, we will be using ext4 without the journal feature)

# mkfs.ext4 /dev/sda1 -O ^has_journal
# mkswap /dev/sda2

Mount the target root filesystem

# mount /dev/sda1 /mnt

Start the Arch base system bootstrap

# pacstrap /mnt base

The base system packages are being downloaded and installed.
Now let’s chroot into our target system

# arch-chroot /mnt

Before rebooting the system, a proper bootloader is necessary. I personnally like a faster and simpler bootloader (good bye GRUB2!) but your mileage may vary.
Note : as of 2012/07/20 Grub Legacy is not officially supported anymore by Arch, see
So let’s use the good old LILO

# pacman -S lilo

Review and edit lilo.conf

# nano /etc/lilo.conf

# /etc/lilo.conf
# This line often fixes L40 errors on bootup
# disk=/dev/hda bios=0x80

Then write up the bootloader

# lilo -v

Now you can reboot your system!

Post-installation configuration

Login as root (empty password)

# loadkeys fr

The / filesystem may be mounted read-only already. In order to make changes we are remounting it writable.

# mount -o remount,rw /

Edit /etc/fstab for the change to be permanent

# nano /etc/fstab

Add the line :

Set a new root password

# passwd

As usual with Arch, we begin with the configuration of /etc/rc.conf

# man rc.conf
# nano /etc/rc.conf

Setup your network settings, I use dhcp so leave untouched the network section.
Setup your hostname

# echo >/etc/hostname archweb


# ln -s /usr/share/zoneinfo/Europe/Paris /etc/localtime

Uncomment preferred locale in /etc/locale.gen, and generate it

# nano /etc/locale.gen && locale-gen

Set locale

# echo >/etc/locale.conf ‘LANG=fr_FR.UTF-8’


# echo >/etc/vconsole.conf ‘KEYMAP=fr’
(Reboot system to ensure everything works well)
Install and start sshd so that you can remotely connect on this machine

# pacman -S openssh && rc.d start sshd

Create a regular user “guest”

# adduser guest

additional groups : video,audio
(or else your user will not have sound playback or access to webcam)
Also set a password for this user.

Sound configuration

# pacman -S alsa-utils && alsactl init
# alsamixer; alsactl store
# aplay /usr/share/sounds/alsa/Noise.wav

add then alsa daemon to rc.conf

# nano /etc/rc.conf

Install some more useful system packages

# pacman -S net-tools htop lzop

(lzop needed for tar –lzop)
Minimal X.Org installation

# pacman -S xorg-server xorg-xinit xorg-setxkbmap xorg-xmessage xf86-input-evdev xf86-video-vesa xf86-video-fbdev rxvt-unicode feh

(some more dependencies will of course be automatically installed)
If you now the right xf86-video-* driver for your graphics card, add it to the line. For my test system with Intel integrated graphics, I used xf86-video-intel.
ATI users will add xf86-video-ati, nVidia users will add xf86-video-nv OR xf86-video-nouveau.
When the target system is a removable media (usb pendrive), it’s a good idea to install them all.
Use urxvt terminal emulator instead of xterm

# ln -s /usr/bin/urxvt /usr/bin/xterm

Installing a lightweight window manager
For this howto I chose to use the lwm window manager. It’s ultra-lightweight, mouse-driven, requires no configuration and has no hotkey shortcuts.

# pacman -S lwm

If you prefer another, super lightweight, non-tiling wm, I suggest fluxbox (configuration is a bit easier than openbox).
ratpoison also come to mind, but it’s more keyboard-oriented.
spectrwm makes a good choice for a minimalist tiling wm (and doesn’t require compilation unlike dwm).

Login as regular user “guest”

$ cat >~/.xinitrc <<EOF
setxkbmap fr &
urxvt &
exec lwm

and start X

$ startx
Right now the whole system uses approx. ~800M of disk space.

Installing and configuring the web browsers
It may be interesting to install several web browsers (Firefox, Chromium, Opera, and Surf) and let the user chose which one he wants to use (see that later)
Note : Surf ( ) is a minimalist, fast non-tabbed webkit-based browser.
From now on I’ll focus mainly on Firefox as it is still my favourite browser (although Chromium and Opera show quite faster startup times)

# pacman -S chromium opera gstreamer0.10-base-plugins gstreamer0.10-good surf firefox arch-firefox-search firefox-adblock-plus firefox-noscript

TIP : install only Opera if short on disk space (will use ~100M of disk)
Install some nicer fonts as well

# pacman -S ttf-liberation ttf-ubuntu-font-family ttf-droid

Then configure the fonts in Firefox accordingly (Edit>Preferences>Content>Advanced)
(also pick a nice theme/persona for your Firefox while you’re at it, since the default GTK theme looks quite bland!)
You may want to install the dreaded flash and java plugins, and a small pdf viewer

# pacman -S flashplugin jre7-openjdk icedtea-web-java7 epdfview

TIP : avoid java if short on disk space.
TODO : chromium config
TODO : opera config

Automatically start the web browser with X (and with a custom start page)
Here we make sure that the browser is restarted automatically when the user closes it.
Also, which web browser to start is determined by the kernel command line (and thus by the boot menu 🙂 )
We are also setting a background wallpaper using feh. Download a nice wallpaper from (for instance) and save it to /usr/share/wallpaper/wallpaper.jpg
(or use the official Arch wallpaper package -around ~10M- : archlinux-wallpaper)
Note : this is the system-wide xinitrc

# nano /etc/X11/xinit/xinitrc

Delete the lines below (and including) the “twm &”, and append the following lines :

# xinitrc
# default url to start browser to
# for debugging purposes
# set wallpaper
feh –bg-scale –no-fehbg /usr/share/wallpaper/wallpaper.jpg
# set keymap, eventually start some program and of course our window manager
setxkbmap fr &
#urxvt &
lwm &
# parse browser= parameter from kernel command line
# to determine with web browser to use (if parameter not found, a default browser is used)
CMDLINE=`cat /proc/cmdline`
for x in $CMDLINE; do
[[ $x = browser=* ]] || continue
WEB_BROWSER=`printf ‘%b\n’ “${x#browser=}”`
# for debugging purposes
#echo >/tmp/debug.html “System startup time (s) (before starting browser): `cat /proc/uptime`”
# start browser, infinite loop
# if the browser cannot be started, display an error message
while true;
$WEB_BROWSER $URL || xmessage -center -buttons “Ooops!” “For some reason, the web browser ‘$WEB_BROWSER’ could not start!”
sleep 1
# this is never executed
exit 0

Automatically login (as regular user “guest”) into X at boot time

# nano /etc/rc.local

su – guest -c startx &
exit 0

(make sure /usr/bin/Xorg has the setuid bit set, this is normally the default in Arch)

# chmod u+s /usr/bin/Xorg

Now reboot your system and see.

It is possible to gain a great amount of boot time by using several techniques.
Recompress the initramfs with LZO (fastest decompression)

# /etc/mkinitcpio.conf


and rebuild

# mkinitcpio -p linux
Start daemons in background in /etc/rc.conf

# nano /etc/rc.conf

DAEMONS=(syslog-ng @network @crond @dbus @alsa @sshd)

(You may experience that the browser is unable to connect to the network for several seconds right after booting, that’s because network initialization hasn’t yet completed, especially when using dhcp. Just wait some more seconds and refresh the page)
Might be better to use a fixed non-dhcp network configuration. Or you can use an offline default start page (~/index.html).
Use some kernel boot-time options in the bootloader’s configuration (here for lilo) : quiet and fastboot.
Also use a trick to hide the console messages during boot, by redirecting output to the serial console (console=ttyS0).
And change lilo’s default menu colors with a slightly less ugly, Arch-inspired scheme, while we’re at it!
You have to create a nice 640x480x256 colors bmp image or use a lilo provided one.

# nano /etc/lilo.conf

# /etc/lilo.conf
# set console vga mode to 1024x768x16 when booting
#menu-title = “Arch Linux Lightweight Web Kiosk”
#menu-scheme = Bk:Ck
# path to your custom bitmap image
# or else use an image provided with lilo
# position of menu entries
# hide menu timer (but timeout still active)
# the “browser=” parameter determines which web browser will be started
append=”quiet fastboot console=ttyS0 browser=firefox”

append=”quiet fastboot console=ttyS0 browser=chromium”
append=”quiet fastboot console=ttyS0 browser=opera”
append=”quiet fastboot console=ttyS0 browser=surf”
# Maintenance mode : boot in single mode “S” with the fallback initramfs

Don’t forget to update lilo

# lilo -v
Using disks UUIDs
If you have installed this system on a removable drive, you’ll WANT to use UUIDs. Fortunately, LILO understands this well.
First generate the UUIDs for your partitions

# blkid

Then update /etc/fstab accordingly (replace /dev/sda1 with UUID=”copy_and_paste_uuid_from_blkid_s_output”)

UUID=”796e578f-0b2e-4994-8f2b-84fbd9f60c66″ /   ext4    defaults,noatime,ro     0       1

(do the same for other disk partitions when needed)
Then update lilo.conf

# lilo-uuid-diskid

This will change the “boot=” option in lilo.conf with the disk’s id.
But you may also need to manually replace the “root=” value for every kernel entry.
This would give (excerpt) :

#        root=/dev/sda1

When done, update lilo

# lilo -v

Trimming down the system a bit
At this stage, our system has grown up to ~1.5G of disk space, which is a quite lot already.
Let’s try to save some megs here and there.
Show installed packages

# pacman -Q |more

Remove core manpages? You decide!

# pacman -Qi man-pages

takes ~15M of disk space

# pacman -R manpages

NOTE : this will not erase the man pages from non-core programs, so we do it

# rm -rvf /usr/share/man/*

Erase the documentation, the include and source files as well (*except* if you plan to compile things!)

# rm -rvf /usr/share/{doc,gtk-doc}/*
# rm -rvf /usr/{include,src}/*

Remove useless locale files (/usr/share/locale/) *except* some

# shopt -s extglob
# rm -rvf /usr/share/locale/!(fr|en_US|locale.alias)

(here we only keep fr and en_US locales, the rest is deleted!)
(Too bad Arch doesn’t have an official equivalent to Debian’s localepurge)
NOTE : don’t forget to do this again whenever you add some packages or update the system
See disk usage per directory and hunt down some more useless stuff (for our usage anyway!) at your convenience

# du -m -d 1 / |sort -g
# du -m -d 1 /usr |sort -g
Some other packages not needed for our setup (won’t save many megs…)

# pacman -R lvm2 mdadm xfsprogs reiserfsprogs ppp

And finally clean pacman’s downloaded packages (should gain at least ~200M!)

# pacman -S –clean –clean

(yes, –clean two times to force full removal of cached packages)

Reduce the number of ttys
This will save some precious RAM on low-memory systems.

# nano /etc/inittab

Comment out some ttys (leave at least the first one!)

Prelink binaries for faster program startup (Optional)

# pacman -S prelink
# prelink -a

Remove terminal emulators
(so that users can’t execute a terminal using the various window manager’s hotkeys, and thus can’t kill the window manager)

# pacman -R rxvt-unicode

(Note : You’ll still be able to access the system via SSH if needed)
Forbid user from exiting the window manager.
lwm does not have a hotkey to quit, so it’s all good. For another wm it will be necessary to disable the corresponding hotkey shortcut or menu entry.
Disable Kernel SysRq keys ( )

# nano /etc/sysctl.conf

Make sure the kernel.sysrq value is set to 0 (this is normally the default in Arch Linux).
Disable tty switching in X (See man xorg.conf for reference)

# cat >/etc/X11/xorg.conf.d/10-custom.conf <<EOF
Section “ServerFlags”
Option “DontVTSwitch” “true”
Option “DontZap” “true”
Make sure iptables is installed

# pacman -S iptables

Configure iptables. Here I choose to only allow HTTP/HTTPS ports in output, and deny everything in input (except ping and SSH in)
TODO : allow FTP out
TODO : SSH connection rate limiting

# nano /etc/iptables/iptables.rules

# Already established input connections
# allow traffic on loopback
-A INPUT -i lo -j ACCEPT
# allow ping in
-A INPUT -p icmp –icmp-type echo-request -j ACCEPT
-A OUTPUT -p icmp –icmp-type echo-reply -j ACCEPT
# allow ping out
-A OUTPUT -p icmp –icmp-type echo-request -j ACCEPT
-A INPUT -p icmp –icmp-type echo-reply -j ACCEPT
# allow dns traffic out
-A OUTPUT -p udp –dport 53 -j ACCEPT
# allow SSH in
-A INPUT -p tcp –dport 22 -m state –state NEW,ESTABLISHED -j ACCEPT
-A OUTPUT -p tcp –sport 22 -m state –state ESTABLISHED -j ACCEPT
# allow HTTP/HTTPS out
-A OUTPUT -j ACCEPT -p tcp -m multiport –dports 80,443
# reject the rest
-A INPUT -p tcp -j REJECT –reject-with tcp-reset
-A INPUT -p udp -j REJECT –reject-with icmp-port-unreachable
-A INPUT -j REJECT –reject-with icmp-proto-unreachable

Start the firewall

# rc.d start iptables

Don’t forget to update /etc/rc.conf to start iptables (before network daemon) at boot time

# nano /etc/rc.conf

DAEMONS=(syslog-ng iptables @network @crond @alsa @sshd)

Test your firewall from another machine with nmap
TCP scan

# nmap <ip of target machine>

UDP scan

# nmap -sU <ip of target machine>
Making the system read-only
Several steps are needed to make sure the system will run correctly when the root filesystem is turned read-only.
Link /var/lock and /var/run to /run (tmpfs)

# ln -sf /run/lock /var/lock
# ln -sf /run /var/run

Link /etc/resolv.conf to /tmp/resolv.conf (this is necessary for the dhcpcd client to work properly)

# ln -sf /tmp/resolv.conf /etc/resolv.conf

Edit /etc/fstab
add the following lines :

tmpfs           /tmp            tmpfs   nodev,nosuid,rw      0       0
tmpfs           /var/tmp      tmpfs   nodev,nosuid,rw      0       0
tmpfs           /var/log       tmpfs   nodev,nosuid,rw      0       0

And don’t forget to set / with the ro attribute :

UUID=xxxxxxxxxxxxx       /       ext4    defaults,noatime,ro     0       1

Note : with read-only filesystems we use no swap partition. So you have to ensure your computer has enough RAM to operate properly (at least 256M suggested).

The trick is that the user profile will be copied into ram (/tmp) at boot time, so we ensure the profile does not contain unwanted data and is the smallest possible to retain fast boot times :
– delete bash history, temporary files and other unwanted data
– delete history and cache for ALL web browsers (you may as well completely disable disk cache in each browser)
– delete downloaded files
To see how big is your profile

$ du -m -d 1 ~ |sort -g

When everything is ready, create the archived /home (use of lzop compression algorithm for the fastest decompression)

# tar cvf /home.tlzop /home –lzop
TIP: If you prefer to use a completely new, empty profile :
– do not generate the archived home (the script in rc.local will automatically detect when it does not exist)
– (optionally) erase /home/guest/

Finally, edit /etc/rc.local :

# nano /etc/rc.local

# /etc/rc.local: Local multi-user startup script.
# login name of regular user (user account must exist!)
# read-only filesystem trick :
# extract /home directory archive to /tmp (in tmpfs)
# and bind it to /tmp/home
# create (empty) home/$MYUSER directory anyway just in case the archive doesn’t exist
mkdir -m 700 -p /tmp/home/$MYUSER && chown $MYUSER /tmp/home/$MYUSER
# extract home archive if it exists (if not the user profile will be empty)
[[ -f “$MYHOMEARCHIVE” ]] && echo -n “Extracting archived home directory $MYHOMEARCHIVE to /tmp … ” && tar xf “$MYHOMEARCHIVE” -C /tmp && echo “OK”
mount –bind /tmp/home /home
# start X as regular user
su – $MYUSER -c startx &
exit 0

The final touch
Create a default homepage document

# mkdir -p /usr/local/share/doc/homepage/
# cat >/usr/local/share/doc/homepage/index.html <<EOF
<h1>Welcome to the Arch Linux Simple Lightweight Web Kiosk!</h1>
<h2>Useful hotkey shortcuts<h2>
lorem ipsum…

Now reboot your system, it will running in read-only mode. You may see some minor, non-blocking errors during boot (to be fixed… see /var/log/boot for more info).

Maintenance (“admin”) mode
Want to update your system? Or add some modifications?
Just boot in maintenance mode, enter your root password, and remount the filesystem so that it is writable again :

# mount -o remount,rw /

If you want to update, start the network first

# dhcpcd eth0
# pacman -Syu

Note: when updating /home profiles, don’t forget to finally re-generate the /home.tlzop archive!

# tar cvf /home.tlzop /home –lzop

End of modifications

# reboot

(please note, on next reboot the system will -of course- be read-only, as in /etc/fstab)

How to create a bootable usb drive (2G or higher capacity needed)
If you have installed this system on a non removable drive and want to create a bootable usb drive from it, follow these steps :
Boot in Maintenance mode
Plug your use drive and prepare it (all data will be erased!)

# cfdisk /dev/sdX

(adjust with your actual drive letter)
Create one bootable ext4 partition, save and quit cfdisk.
Create an ext4 filesystem (still with no journalling to prevent premature wear & tear of flash drives) and with a label (important)

# mkfs.ext4 /dev/sdXy -O ^has_journal -L archusb001 -T small

for information purposes

# tune2fs /dev/sdXy

Now mount usb drive and copy filesystem

# mkdir /tmp/usb
# mount /dev/sdXy /tmp/usb
# time cp -avr {/bin,/boot,/etc,/home,/lib,/opt,/root,/sbin,/srv,/usr,/var} /tmp/usb

(will take a while…)
Now we need to install the bootloader, so we chroot into our /tmp/usb

# mkdir /tmp/usb/{dev,proc,run,sys,tmp}
# mount –bind /dev /tmp/usb/dev
# mount –bind /proc /tmp/usb/proc
# mount –bind /sys /tmp/usb/sys
# chroot /tmp/usb

adjust /etc/fstab, replace UUID with LABEL

# nano /etc/fstab


lilo.conf too needs several adjustments

# nano /etc/lilo.conf

adjust the boot= line with your usb drive letter, or remove it since we can specify it from the command line :


adjust every “root=” line in the image sections with the usb drive’s root partition label


finally, adjust every “initrd=” line (we want to be use the fallback image so that different hardware will be recognized)


Your lilo.conf now should look like (“optimized” version) :
<paste lilo.conf>
Save and write lilo to disk /dev/sdX

# lilo -v -b /dev/sdX

Another important thing : we need to rebuild the initramfs with the “usb” hook added

# nano /etc/mkinitcpio.conf

add “usb” just before “filesystems” in the HOOKS array :

HOOKS=”base udev autodetect pata scsi sata usb filesystems usbinput fsck”

# mkinitcpio -p linux

Done, unmount everything and reboot on your usb drive!

# exit
# cd && umount /tmp/usb/{dev,proc,sys,.}

check filesystem just to be sure

# fsck.ext4 -y /dev/sdXy
# reboot


I’m sure there’s still plenty of details I have forgotten, and plenty of room for improvement. Your feedback is appreciated.
Now it’s your turn to play, share your experiences, boot times, etc!

Useful links and resources


3 thoughts on “Building a simple lightweight web kiosk system with Arch GNU/Linux

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s