This document describes how I went about turning a set of cheap ~200MHz diskless PCs into internet kiosks. This is how the kiosks at DNA Lounge worked from late 2000 through 2006. Prior to that, I had experimented (somewhat unsuccessfully) with using laptops as kiosks. After that, I went to a "thin client" approach, instead of the "fat client" approach described here.
For an explanation of what my goals were, and why things no longer work as described here, go back.
The plan here, in the "fat client" world, was to have the machines run all their applications locally, but have no local disks: everything would be mounted via NFS from a remote file server. Users would auto-login as a "guest" user on the client machine, which would run X11, a web browser, a basic GNOME desktop, etc.
As much would be mounted shared and read-only as possible; and when the machine rebooted, everything would reset to a default state.
The hardware we settled on was:
We found that the monitors held up great, but the ThinkNICs were a little too heat-sensitive. We ended up cutting holes into the cases and attaching an additional fan to each, which helped. Sadly, their onboard video tended to have a bad failure mode: if the machine had ever overheated, then from then on, the video signal would be ``staticky'', looking like a bad cable. Apparently heat makes something in the video subsystem fry this way. But most of them had overheated at least once before we attached the fans, so most have noisy video now.
We haven't had to replace the keyboards or trackballs as often as you'd think, though one does tend to need cleaning or minor repair (de-gunking, or replacing a key or two) every couple of weeks. Getting indestructible, battle-hardened keyboards would not have been worth the cost. It took some time to find trackballs whose balls could not be removed without tools, but we did. (That was important, or the balls would have gone missing every night.)
In December 2002, we began upgrading the machines, and thanks to
Moore's Law, they got a lot faster without getting much more expensive.
We're still using the same monitors (they've held up great) but we
built new machines from scratch:
The kiosks boot over the network, NFS mounting everything, and running applications locally. (The ThinkNIC comes with a bootable CD that basically launches you directly into Netscape, but that's not what I wanted: I want a full desktop environment, not just a browser.)
Instead of booting over the network, I could have just burned my own set of bootable CD-ROMs for them: these CDs would contain a kernel, a basic root file system, and enough in the way of config files to boot up and mount everything else over NFS. But I decided to go with a full netboot for two reasons:
When I started this project in late 2000, I considered setting up the kiosks as remote X terminals, with all the applications running on a single large remote server machine, but decided against it. Especially once the kiosks were upgraded to the 200MHz range in 2002 (instead of the original 90MHz range), they were absolutely performant enough to do web browsing and general communications-oriented tasks here in this modern age.
I also took a look at the Linux Terminal Server Project, but that didn't do quite what I wanted. They've got a really nice setup, and it's very easy to install, but it's heavily oriented toward remote X terminals that run applications on the server. Since I want to only run applications locally, on the diskless machines, it looked like I wouldn't have ended up using most of what LTSP does: they're about scaling down Linux as far as possible so that you can still run X on small machines; I want to end up with a more complete local Linux system than that.
In 2006, I changed my mind about that; see above.
Here are the steps I followed in creating this system:
The basic idea here is that there will be a subnet where one machine functions as the NFS, DHCP, and TFTP server for the kiosk machines, which are on that same network but do not have disks of their own.
To simplify matters, the Linux installation running on the server is the Linux installation that will be running on the kiosks: so you can update software on the server machine, and all the kiosks will see those updates, since they're running out of (pretty much) the same file system.
I've done this with Red Hat Linux 6.1, 7.0, 7.3, and 9. If you're using a different distribution, your mileage may vary. These instructions currently apply to Red Hat 9.
Make sure the server has these packages installed, configured, and activated:
If you have firewall rules (e.g., ipchains) installed on the server machine, make sure that all those services are accessible from other machines on its network. Do this first: if firewalling problems bite you later, while you're messing with the diskless machines, the problems will be very obscure.
If you're using ipchains, you'll need to do something like this:
# NFS requires 111/tcp (sunrpc/portmapper) and *all* UDP ports. # ipchains -A input -p tcp -s $SUBNET -i eth0 -d 0/0 111 -j ACCEPT ipchains -A input -p udp -s $SUBNET -i eth0 -d 0/0 -j ACCEPT # These ports are required by bootp, tftpd, and PXE. # There are also a handful of udp ports that need to # be open, but we've already opened those, above. # ipchains -A input -p tcp -s $SUBNET -i eth0 -d 0/0 67 -j ACCEPT ipchains -A input -p tcp -s $SUBNET -i eth0 -d 0/0 69 -j ACCEPT ipchains -A input -p tcp -s $SUBNET -i eth0 -d 0/0 4011 -j ACCEPT ipchains -A input -p tcp -s $SUBNET -i eth0 -d 0/0 1759 -j ACCEPT
Doing this with iptables is left as an exercise to the reader. I just turned iptables off, and don't run any non-public services.
Create a user to own the kiosk files.
Take care that the user and group numbers will be the same on the server and on all the clients.
groupadd -g 666 guest useradd -u 666 -g guest -s /bin/false -c 'Public Terminal' guest chmod a+rx /home/guest
Build a kernel for the kiosk machines.
As I said above, I was initially using ThinkNIC diskless machines. The stock Red Hat kernel didn't work for me, because it doesn't have support for kernel IP autoconfiguration built in (meaning it can't be used to network-boot.) So I built a new kernel for the kiosks:
Once it's built, put the new kernel (the bzImage file) in /home/guest/kiosk.vmlinuz.
BPBatch is a network boot-loader. If you're using different hardware, you might be able to get away with using etherboot or some other network-booting package; with these machines I had to use BPBatch in order to get the ThinkNIC to load up and run a kernel over the network.
Compile and install BPBatch in /home/guest/bpb:
The only files that need to be in the bpb/ directory are: bpbatch.P, bpbatch.ovl, and bpbatch.hlp. Plus:
ln -s bpbatch.P bpbatch.B ln -s bpbatch.P bpbatch
DHCP is how the remote machines get their IP addresses, and how they know which boot file to pull down (with TFTP.)
Each kiosk also needs to boot slightly differently (for example, they each have different IP addresses and names.) We have a single bpbatch script that boots all of the kiosks, and it reads a few parameters that are passed in by DHCP.
Here is the /etc/dhcpd.conf file:
option domain-name "dnalounge.net";
option domain-name-servers kiosk;
option routers 172.28.4.1;
use-host-decl-names on;
ddns-update-style none;
option bpb-args code 135 = text; # how BPB itself gets arguments
option kiosk-id code 133 = text; # we pass the kiosk number and kernel
option kiosk-kernel code 134 = text; # file name to our BPB script here.
subnet 172.28.4.0 netmask 255.255.255.0 {
range 172.28.4.81 172.28.4.254; # allocate only addresses above kiosk16
}
group { # Kiosk hosts
filename "bpb/bpbatch"; # boot file
option bpb-args "kiosk"; # the BPB script ("kiosk.bpb")
host kiosk01 {
option kiosk-id "01";
fixed-address kiosk01;
option kiosk-kernel "realtek";
hardware ethernet 00:10:DC:5F:B9:D5;
}
... and so on ...
}
I'm not going to go into too much detail about what the above means; the dhcpd and bpbatch documentation explains it pretty well. Basically what's going on is, that file tells dhcpd to inform the kiosks of their IP address, and that their boot file. They should download and run the boot file (bpbatch) with a particular string as its argument. Once bpbatch is running, it downloads the file named by its argument (in this case, kiosk.bpb) and interprets the contents of that file.
The only tricky bit is the "option" stuff. Various parameters can be passed via DHCP/BOOTP. They're identified by number, and many of them have predefined semantics (IP address, etc.) Code 135 is used by BPBatch: that's where it gets its "command line argument", the name of the script it should download and interpret.
I'm passing in two additional parameters: the kiosk ID number, and the kernel file to boot. I picked codes 133 and 134 for those out of thin air.
Here's the /tftpboot/kiosk.bpb file I'm using:
set CacheNever="ON"
set CacheAlways="OFF"
set NAME="$BOOTP-Host-Name"
set ADDR="$BOOTP-Your-IP"
set NFS= "$BOOTP-Server-IP"
set GATE="$BOOTP-Routers"
set MASK="$BOOTP-Subnet-Mask"
set ID= "${BOOTP-Option-133}"
set KERNEL="${BOOTP-Option-134}.vmlinuz"
set NAME="kiosk$ID"
set INIT = "root=/dev/ram init=/rd/bin/init0"
set IP = "ip=${ADDR}:${NFS}:${GATE}:${MASK}:${NAME}::"
set ARGS = "$INIT $IP SERVER=$NFS GATEWAY=$GATE ID=$ID nomodules nousb"
set trace="ON"
linuxboot "$KERNEL" "$ARGS" "kiosk.rd"
Export some file systems by editing /etc/exports: There are three file systems that each client will need to mount:
The first two sets of files can (and must) be mounted read-only; the third set of files must be mounted on the client writably, and cannot be shared with any other client.
For the first set (files shared between the client and server) we will just nfs export the server's root directory:
/ (ro,async,all_squash)
The all_squash option means that all file access from the client will be remapped to behave as accesses by nobody: that means that if you have files or directories on the server that you wish to remain unreadable by the client, you can just make them not be globally readable (e.g., this means the client won't be able to read the server's /etc/shadow or /var/log/messages, even as root.)
If you have more than one partition on the server that you want to share, export those in the same way:
/usr *(ro,async,all_squash) /var *(ro,async,all_squash)
For the second set of files (files shared among all clients, but which are different on the clients than on the server), we'll keep those under /home/guest/:
/home/guest/share *(ro,async,no_root_squash)
Note that in this case, we specify no_root_squash, meaning that reads and writes as the root user will be honored (e.g., so that the clients can read their own /etc/shadow.)
For the third set of files (files unique to each client, and also used as the writable space for each client), we'll need one directory for each client. So if you have 16 kiosks, you'll need 16 of these directories:
/home/guest/01 kiosk01(rw,async,no_root_squash) /home/guest/02 kiosk02(rw,async,no_root_squash) /home/guest/03 kiosk03(rw,async,no_root_squash) ...etc...
The client machines will refer to the server's root directory as /ro, to /home/guest/share as /ro2 and to /home/guest/nn/ as /rw. It will make things immensely easier to understand if these names are usable on the server machine during installation, so create some symlinks for them now:
ln -s / /ro ln -s /home/guest/share /ro2 ln -s /home/guest/01 /rw
It would be sensible for the writable kiosk directories (01/, etc) to be on their own partition on the server, so that it's not possible for user activity on the kiosk to fill up an important partition on the server, and cause a denial of service. Even better if each of these directories is on its own partition, so that the kiosks can't interfere with each other, either. 10M is probably more than sufficient: around 2M is needed for various files, and the rest can be used by /tmp and the guest user's home directory.
Perhaps this could be accomplished with disk quotas, but I've never tried to do that.
First, we will create the 01/ directory: the directory of files which need to be writable on the first client, and that aren't shared with any other clients. (We'll make 02, 03, and so on by copying this directory when we're done.)
cd /home/guest mkdir 01 chown guest.guest 01 chmod 755 01
We only need to create directories in here which the client needs to write to, or which contain files that the client needs to write to.
cd 01
mkdir boot dev etc home tmp var
mkdir -p etc/ntp \
etc/ssh \
etc/ntp \
home/guest \
lib \
var/lock/console var/lock/subsys \
var/cache var/log var/preserve var/run var/run/netreport \
var/spool/at var/spool/cron \
var/spool/mqueue var/spool/clientmqueue \
var/state/misc \
var/lib \
var/tmp
chmod 1777 var/tmp
chown ntp.ntp etc/ntp
chown smmsp.smmsp var/spool/clientmqueue
chmod 770 var/spool/clientmqueue
We need a few writable device files in /dev, and the rest can be symlinks to the ones on the server. (The reason these devices need to be writable is because various programs try to chown and chmod them, and will object if they are unable to.)
(Possibly using devfs instead of a real directory for /dev would simplify matters, but I don't know how to do that.)
Copy over the devices we need:
cp -a /dev/{audio*,dsp*,initctl,log,tty,tty?,ttyS?,cua?,ttyp?,pty*} \
/dev/{console,null,mem,mouse,psaux,gpmdata,*random} \
dev/
Create placeholders for these files, which we will fill in eventually:
touch boot/kernel.h \
etc/HOSTNAME \
etc/issue \
etc/issue.net \
etc/ssh/ssh_host_key \
etc/ssh/ssh_host_key.pub \
etc/ssh/ssh_host_rsa_key \
etc/ssh/ssh_host_rsa_key.pub \
etc/ssh/ssh_host_dsa_key \
etc/ssh/ssh_host_dsa_key.pub \
etc/ssh/ssh_random_seed \
etc/ssh/moduli \
var/lib/random-seed \
var/log/lastlog \
etc/ntp/drift
Now, in each directory we've created, fill it with symlinks to every existing file in /ro (excepting the files we've already created.) Ignore the ``File exists'' and ``cannot overwrite directory'' errors.
for d in `find * -type d`; do ln -s /ro/$d/* $d done
But not here:
rm var/{log,lock,lock/subsys,preserve,run,spool/mqueue}/*
rm -rf var/cache/*
rm etc/named.* var/named
rm etc/ntp/{drift,keys}
cp -p /etc/ntp/{drift,keys} etc/ntp
rm -rf tmp
ln -s var/tmp tmp
rm etc/named.* var/named
rm -rf home
mkdir -p home/guest
chown guest.guest home/guest
ln -s /ro2/home/guest-ro home
Next, create /home/guest/share/ (AKA /ro2), the directory shared among the clients, but not shared with the server. This directory tree only needs to contain those directories in which we needed to make client-specific changes from the server's configuration. If we needed to change a file in some directory, then that file will be a real file, and everything else in that directory will be a symlink.
cd /home/guest
mkdir share
chown guest.guest share
chmod 755 share
cd share
mkdir etc home root var
mkdir -p etc/X11 \
etc/cron.d \
etc/cron.daily etc/cron.hourly \
etc/cron.monthly etc/cron.weekly \
etc/pam.d \
etc/rc.d/rc0.d \
etc/rc.d/rc1.d \
etc/rc.d/rc2.d \
etc/rc.d/rc3.d \
etc/rc.d/rc4.d \
etc/rc.d/rc5.d \
etc/rc.d/rc6.d \
etc/ssh \
etc/sysconfig/network-scripts \
var/empty/sshd
We also need a local copy of these files (since the versions in /ro are only readable by root, and thus, won't be readable by the client):
rm etc/securetty etc/pam.d/ssh etc/pam.d/sshd cp -p /etc/securetty etc/ cp -p /etc/pam.d/ssh /etc/pam.d/sshd etc/pam.d/
To make root logins on the console work, this is necessary on Red Hat 7.0:
for n in 1 2 3 4 5 6 7 8 9 ; do echo /rw/dev/tty$n >> etc/securetty done
Now fill this directory with symlinks to the files and directories we created earlier in the /rw (01, 02, etc) directory.
ln -s /rw/tmp . ln -s /rw/tmp var ln -s /rw/var/* var ln -s /rw/etc/issue* etc ln -s /rw/etc/HOSTNAME etc ln -s /rw/home/guest home ln -s /rw/etc/ssh/* etc/ssh ln -s /rw/etc/ntp etc
Now fill in the blanks: all of these remaining files in these directories are shared with the server, and so we need to add some more symlinks to /ro. (Again, ignore the ``File exists'' and ``cannot overwrite directory'' errors.)
for dir in `find * -type d` do ln -s /ro/$dir/* $dir done
To summarize, at this point, we have two link farms:
Next, it's time to put actual content in the /ro2 directory (AKA /home/guest/share).
We don't need any of the standard cron jobs on the kiosks:
cd /home/guest/share rm etc/cron.*/*
Make sure that the /ro2/etc/rc?.d/ symlinks are pointing to the right place (they should be pointing to rc.d/rc?.d/, but because of the way we did things above, they will be pointing to /ro/etc/rc.d/rc?.d/, which is wrong.) We need this so that it's possible for the kiosks to run a different set of services than the kiosks' server.
for n in 0 1 2 3 4 5 6 ; do rm etc/rc$n.d ln -s rc.d/rc$n.d etc/rc$n.d done
We don't need any of the ``kill'' scripts, since the kiosks will be safe to simply power off, without a long and involved shutdown process:
rm etc/rc.d/rc?.d/K*
You definitely don't want to be carrying over the host machine's PNP, NFS, and module configuration:
rm etc/isapnp.conf etc/exports etc/conf.modules etc/modules.conf
Definitely don't need or want these:
rm etc/rc.d/rc?.d/*dhcpd rm etc/rc.d/rc?.d/*kudzu rm etc/rc.d/rc?.d/*linuxconf rm etc/rc.d/rc?.d/*named rm etc/rc.d/rc?.d/*nfs rm etc/rc.d/rc?.d/*nfslock rm etc/rc.d/rc?.d/*pcmcia rm etc/rc.d/rc?.d/*portmap rm etc/rc.d/rc?.d/*pxe
Probably don't want these. Also check for any other services that you might have running on the server machine that you don't need on the kiosk, and delete those symlinks too.
rm etc/rc.d/rc?.d/*httpd rm etc/rc.d/rc?.d/*inet rm etc/rc.d/rc?.d/*xfs rm etc/rc.d/rc?.d/*xinetd rm etc/rc.d/rc?.d/*gpm rm etc/rc.d/rc?.d/*lpd rm etc/rc.d/rc?.d/*portmap
It's better for /etc/mtab to actually reflect reality:
rm etc/mtab; ln -s /proc/mounts etc/mtab
The kiosks should have their own hosts file; it simplifies things if there is an entry in there that lists the IP of the server machine (which we will symbolically call ``kiosk'': the kiosks themselves will be named ``kiosk01'', etc.) But we only need the two entries; DNS will take care of everything else later.
rm etc/hosts/ro2/etc/hosts:
127.0.0.1 localhost 192.168.0.10 kiosk # the NFS server
The kiosks need their own fstab. This at least needs to have entries for /proc and /dev/pts; and should also have entries for any other remote file systems you want to mount, and any local devices you want to access.
rm etc/fstab/ro2/etc/fstab:
kiosk:/ / nfs ro,nolock 0 0 # kiosk:/usr /ro/usr nfs ro,nolock 0 0 # kiosk:/var /ro/var nfs ro,nolock 0 0 # kiosk:/home/local /ro2/home/local nfs ro,nolock 0 0 /dev/cdrom /mnt/cdrom iso9660 noauto,owner,ro 0 0 #none /proc proc defaults 0 0 none /dev/pts devpts gid=5,mode=620 0 0
It doesn't actually need entries for /ro2, /rw, and /proc, because of the semi-magic way we will be mounting those, but you can add them if it makes you feel better.
If you are mounting any file systems other than /ro, /ro2, and /rw, make sure that "netfs" is turned on, so that they get mounted:
You probably need "nolock" on those file systems, unless you're luckier than I was, and NFS locking somehow magically works for you.
ln -s ../init.d/netfs etc/rc.d/rc3.d/S25netfs ln -s ../init.d/netfs etc/rc.d/rc4.d/S25netfs ln -s ../init.d/netfs etc/rc.d/rc5.d/S25netfs
The network is initialized very early on the kiosks, so it's important that Linux not try and mess with it later in the boot process. So, make sure that the kiosks don't try to re-initialize eth0:
rm etc/sysconfig/network-scripts/ifcfg-eth0
And also, change /etc/sysconfig/network to be pretty much a no-op:
rm etc/sysconfig/network/ro2/etc/sysconfig/network:
NETWORKING=yes HOSTNAME=localhost
The kiosks should not share password data with the server: they should have their own copy, and should have different passwords. So first copy the server's password files to the kiosk directory, and then edit it (delete unneeded users, etc.)
rm etc/{passwd,shadow}
cp -p /etc/{passwd,shadow} etc/
The kiosks should send their log messages to the server's syslog daemon. (Make sure port 514/udp is open on the server.) You may need to add the -r flag to the invocation of syslogd in /etc/rc.d/init.d/syslog on the server. Also, note that that has to be a TAB before the at-sign, below: it will silently fail if you use spaces instead of a tab. All hail Unix.
rm etc/syslog.conf/ro2/etc/syslog.conf:
*.* @kiosk
The kiosks need their own inittab, with a few changes.
rm etc/inittab ; cp -p /etc/inittab etc/
First, set the runlevel to 1 (single user.) We'll increase it later as we verify that things work.
id:1:initdefault:
We'll want our own handler for Ctrl-Alt-Del, to shutdown and reboot faster:
ca::ctrlaltdel:/usr/local/sbin/fastboot
(The source for this program is in fastboot.c. Compile it and install it in /usr/local/sbin/ on the server, or whereever it will be visible by the client machines.)
And finally, a password should be required to boot single-user:
~~:S:wait:/sbin/sulogin /dev/console
At this point, we have the file systems set up pretty much the way we want them to be on the kiosks. Now it's time to get the kiosks to actually boot.
Construct a ramdisk root directory.
The boot process works like this:
So first, we need to create the directory tree that we want on the kiosk's root file system (the ramdisk.)
cd /home/guest mkdir rd_root chown guest.guest rd_root chmod 755 rd_root cd rd_root mkdir dev proc ro ro2 rw mkdir rd rd/bin mkdir mnt mnt/cdrom for dir in home var etc root do ln -s /ro2/$dir $dir done for dir in bin sbin lib opt usr do ln -s /ro/$dir $dir done rm home ln -s /rw/home home ln -s /var/tmp tmp ln -s /rw/boot boot touch fastboot
We need a few device files on the ramdisk, since they are used very early:
cp -a /dev/{console,null,ram,systty,tty1,tty2,tty3,tty4} dev
In the ramdisk environment, we only need a single executable program, ``busybox''. This is a program that has bare-bones implementations of a bunch of common Unix utilities. It was designed pretty much for this purpose: bootstrapping Unix in very small environments.
Make sure you're using BusyBox 0.51 or newer.
cp -p /sbin/busybox rd/bin/
If you don't have busybox,
download, build, and install it from
busybox.net.
Make sure you build it static
(with
We also need a few symlinks to that program:
cd rd/bin ln -s busybox sh ln -s busybox mv ln -s busybox rm ln -s busybox ln ln -s busybox mount ln -s busybox umount ln -s busybox sleep
And we need our startup script. Install this file in rd/bin/init0. You can read the comments in that script for details, but it does only these things:
Now that we've constructed the root file system as it should appear in the ramdisk, we need to actually transform it into a ramdisk file. I've written a script that does that:
cd /home/guest/ makerd -v rd_root kiosk.rd
(If you get a ``No space left on device'' error that doesn't make sense, you might have to increase the value of the IMAGESIZE variable in the makerd script.)
Finally, we need to make some of the above files be accessible by TFTP:
cd /tftpboot cp -a /home/guest/bpb . cp -p /home/guest/kiosk.rd . cp -p /home/guest/kiosk.vmlinuz .
Note that recent versions of tftpd chroot into the /tftpboot directory, so you can't have symlinks in there: you have to copy your files in.
Now boot up the kiosk! It should boot in single-user mode, and you shouldn't see any error messages, except possibly about it trying to mount things that are already mounted. Those aren't a problem.
If something went wrong... fix it.
If you get an error about ``can't load init'' or something along those lines, you might find it helpful to construct a ramdisk that just runs busybox as init (instead of my init0 script.) That way, you'll have a shell on the kiosk, and you can figure out what went wrong from there. (Chances are something didn't get mounted properly.)
Once you've gotten init0 to run and give you a real runlevel-1 shell, type ``telinit 3'' at the root shell on the kiosk. This will run a lot more initializations, and should leave you with a running, networked linux system, sitting at a Login: prompt.
Once you get that working, edit /ro2/etc/inittab (on the server) and change initdefault to 3, to make this the default. Reboot the kiosk again (just to make sure), log in again, and try running X.
Start by running xinit, which will give you just an X server and an xterm. It probably doesn't work, because the kiosk probably need their own X configuration, different than the server's:
cd /home/guest/share/etc/X11
rm XF86Config XF86Config-4
cp -p /etc/X11/{XF86Config,XF86Config-4} .
Edit to taste. Here are the XF86Config and XF86Config-4 files I use for a ThinkNIC box (with SiS onboard video) and a KDS Radius S-3F LCD monitor, running at 1024x768x16. (I had to go into the ThinkNIC's BIOS and allocate 4M to video RAM to get this resolution; the default is only 2.5M.) (Also, the first time you start X on the monitor, you seem to need to power-cycle and/or auto-tune it a few times before it stops being confused.)
Beware that the file /usr/X11R6/lib/X11/XF86Config is usually a symlink to /etc/X11/XF86Config, but that it usually looks like this:
/usr/X11R6/lib/X11/XF86Config -> ../../../../etc/X11/XF86Config
That will screw things up for you on the kiosk, so you will probably need to flatten out that symlink on the server:
cd /usr/X11R6/lib/X11 rm XF86Config ln -s /etc/X11/XF86Config .
Initially I was having trouble getting XFree86 4.0.1 working with my system, so I had to start the XFree86-3 driver explicitly:
xinit -- /usr/bin/X11/XF86_SVGA
But later I upgraded to XFree86 4.0.3, and had better luck.
Run ``
Once ``xinit'' works, try ``startx''. That will give you a window manager and desktop environment. But make sure you do this as ``guest'' and not as root: root's home directory is not writable, and Gnome doesn't like that very much.
Once that works, get XDM working: run
Once all that works, you can set the runlevel to 5 in inittab. Congratulations, Now you have a functional remote-mounted Linux machine!
The next step is to make it be an anonymous terminal. The basic idea here is that instead of having XDM pop up a login window, we want it to automatically log in as the user ``guest'' as soon as it starts up. Furthermore, when guest logs in, their home directory should be re-initialized from a read-only known state (so that simply typing Ctrl-Alt-Backspace will reset things to a known state, and get rid of any configuration changes and files that the previous user of the terminal might have made.)
First, log in as the guest user, and set up their X environment the way you want it to be. Log out and save the configuration. Then make a copy of the guest user's directory on the read-only partition:
cp -a /home/guest/01/home/guest /home/guest/share/home/guest-ro
Now you can use some scripts I've written to auto-initialize the guest user, and auto-log-in:
Install these scripts into /usr/local/sbin:
| kiosk-dm | A replacement display manager, for doing password-less logins. It simply invokes kiosk-session. |
| kiosk-session | A session script for kiosk-dm that logs in as guest without prompting for a password, then runs guest's .xsession file. Also uses kiosk-env. |
| xsession | The guest user's .xsession file. It doesn't do much more than invoke gnome-session. |
| kiosk-home-init | This script deletes everything under /home/guest and replaces it with a fresh copy from /home/guest-ro. This is run every time the guest user logs out and then back in again (e.g., when C-Alt-BS is typed.) |
Set up the guest user's .xsession file:
rm /home/guest-ro/.xsession ln -s /usr/local/sbin/xsession /home/guest-ro/.xsession
Now test this configuration by logging in on the kiosk's console as root, and running:
/usr/local/sbin/kiosk-dm
Lather, rinse, debug. Once it works, edit /ro2/etc/inittab and change the ``x'' line to this:
x:5:respawn:/usr/local/sbin/kiosk-dm
Congratulations! You're done.
Ok, but one last thing: let's get sshd working, so that we can ssh in to the kiosks. Each kiosk needs its own unique set of host keys. (With recent versions of ssh, there are actually three keys.)
mkdir /home/guest/share/root/.ssh chmod 755 /home/guest/share/root/.ssh cd /home/guest/01/etc/ssh ssh-keygen -t rsa1 -P '' -f ssh_host_key -C kiosk01 ssh-keygen -t rsa -P '' -f ssh_host_rsa_key -C kiosk01 ssh-keygen -t dsa -P '' -f ssh_host_dsa_key -C kiosk01
When you copy the 01 directory to 02, etc., you'll need to generate a new host key in each of them, as above. The -C argument should be the name of the kiosk machine.
Make sure to make a backup of these keys in a secure place (on another machine) in case you ever need to rebuild the NN/ directories. You'd rather the host keys never change.
The kiosks need different sshd options than the server does:
cd /home/guest/share/etc/ssh rm sshd_config primes cp -p /etc/ssh/sshd_config . cp -p /etc/ssh/primes .
Edit sshd_config and make sure it includes these settings:
PermitRootLogin yes PermitEmptyPasswords no PasswordAuthentication yes IgnoreRhosts yes RhostsAuthentication no FascistLogging yes
This will make it so that you can log in as root remotely, but you can't log in as guest remotely (since guest doesn't have a password.)
Ok, that wasn't really the last thing.
After I installed these kiosks in the club, I spent some time standing around and looking over people's shoulders as they tried to use them.
I found that there were five major usability problems. I believe I have solved four of them...
People kept trying to start using the machines while they were still starting X, and thrashing around. They, of course, found that the machines weren't working very well and seemed really slow. This was a bad way to introduce them!
I came up with a really simple solution to this: the first thing my startup scripts do is set the root window image to be one that has ``Loading, Please Wait'' written on it in inch high letters. The last thing the startup scripts do is set the background image to something prettier.
Of course, this problem went away when we upgraded to newer machines that boot really fast.
The kiosks are set up with a three-button trackball as a pointing device. Experience shows that people had no idea which button to use: I watched people try to select things with the right button (causing menus to pop up, and stay up, cluttering the screen.) Then every now and then they'd hit the left button, and something would work right; but then they'd go back to using the right button or middle again.
This may be partly because people aren't familiar with trackballs, and it may be partly because of the position of the trackball; but the bottom line is, it wasn't working. We needed all three buttons to do the same thing. Opening up the trackball and rewiring it would have been a lot of work, so I found a software solution.
(I decided that, for the applications we're using, it was ok to discard the middle and right buttons: none of the apps that we expect to be used require usage of the right button to work properly, and people can always paste using Edit/Paste on the menubar.)
Normally, the gpm program is used for making the mouse work on the text console and things like that, but you can also use it as a layer that sits between your actual mouse, and the X server. And you can use it to re-map buttons! But, not to re-map all buttons to the same button... For that you need to patch the gpm source (as of gpm-1.20.1.)
--- startup.c.~1~ 2002-12-24 14:57:16.000000000 -0800
+++ startup.c 2004-04-15 23:36:45.000000000 -0700
@@ -60,6 +60,9 @@
{"231","02461357"},
{"312","04152637"},
{"321","04261537"},
+ {"111","04444444"},
+ {"222","02222222"},
+ {"333","01111111"},
{NULL,NULL}
};
Then run gpm like this:
gpm -m /dev/mouse -t ps2 -R msc -B 111
and add this to XF86Config-4 to tell X to read mouse events from gpm's socket instead of the real mouse:
Section "InputDevice"
[ ...etc... ]
Option "Protocol" "MouseSystems"
Option "Device" "/dev/gpmdata"
EndSection
Note that you have to be careful about the mouse protocol: the -t option to gpm tells you what protocol your actual mouse speaks, but the -R redirection stuff seems to only work with the MouseSystems (msc) protocol, so what the X server sees is MouseSystems data.
Every single person assumed that they had to double-click on icons to launch programs, and so every single person ended up launching multiple copies of it. And then that slowed the machine down, so they assumed their click didn't work, and clicked again. And so on.
I solved this by writing a program that sits between the panel icons and the applications: It's called runonce.c, and what it does is, notice when the program it's trying to launch is already running, and in that case, simply raise its window. It also keeps track of programs it has just launched that haven't had time to map their windows yet. Use it by editing the command executed by the panel icons like so:
runonce XTerm xterm -fg black -bg white
Where ``XTerm'' is the application name, application class, or window title of the window that the program maps; and the rest of the line is the command-line used to launch the program.
The Gnome panel doesn't have text titles underneath the icons: it only has tooltips that appear when the mouse hovers over the icon for a while.
Nobody realizes this.
So what they do is, they click on the icons to see what they do. Thus launching applications they don't need, and slowing down the machine while five programs they don't even really want to use try to start up at once.
I solved this by giving the applications different icons that include text in them (``IRC'', ``AIM'', etc.) A better solution, of course, would be for the panel to have an option to display the tooltip text right in the panel. Then it would be localized and everything.
Since this is a public space, people don't log in and log out. They just walk up to the machine, start using it, and walk away when they are done. Consequently, lots of applications were left running, consuming memory and cluttering the screen.
I fixed this by having each machine run a cron job (kiosk-nanny and kiosk-ps-reaper) that periodically wakes up, and, if the screen saver is active, kills off any processes that are not on its list of ``programs that are allowed to still be running.'' This seems to work pretty well.
It seems far more complicated than it ought to be, just to get a Linux system to boot read-only. The files that need to be writable are scattered in far too many places: in my opinion, an out-of-the-box system should never need to touch a file anywhere but /var, and all the other partitions should be read-only by default, but apparently the Linux vendors don't care very much.
Again, I stopped doing things this way in late 2006. Please go back to see how it works today.