Tuesday, October 21, 2014

Improving LTSP scripting facility

One thing that has been a pain with LTSP is that the scripts in /usr/share/ltsp/screen.d which control what launches on the thin client are all contained within the build image.  Any change/customization to those scripts then requires a rebuild/redeployment of the image.

I thought why not just use the tftp client to pull the script from the TFTP server instead.  Turns out this is quite easy to do.  I added the following script to the screen.d directory, and called it "tftp" (which was just based on one of the existing init scripts):


#full path to file (excluding /var/lib/tftpboot)
file=`basename $filepath`

if [ -n "$SERVER" ]; then
    tftp "$SERVER" -c get ${filepath} $script_temp
    # only execute if it has non-zero size.
    if [ -s "$script_temp" ]; then
        mv "$script_temp" /usr/share/ltsp/screen.d/${file}
        chmod 777 /usr/share/ltsp/screen.d/${file}
        exec /bin/bash /usr/share/ltsp/screen.d/${file}
        rm "$script_temp"
        sleep 15
        exec /bin/bash --login
Now in lts.conf I can do somthing like:

SCREEN_01 = tftp /ltsp-trusty/amd64/firefox

Now I have built the "/var/lib/tftpboot/ltsp-trusty/amd64/firefox" script on the TFTP server and it can easily/quickly customized as needed.

Here is an example (requires "apt-get install firefox openbox" in the ltsp-chroot image):


. /usr/share/ltsp/screen-x-common

export HOME="${HOME:-/root}" USER="${USER:-root}"
mkdir -p /root/.config/openbox
echo "/usr/bin/firefox http://somesite.biz" >> /root/.config/openbox/autostart
chmod 755 /root/.config/openbox/autostart

# The following logic is described at the top of xinitrc.
if [ -x /usr/share/ltsp/xinitrc ]; then
    xinit /usr/share/ltsp/xinitrc "$COMMAND" -- "$DISPLAY" "vt${TTY}" $X_ARGS >/dev/null
    eval "xinit $COMMAND -- $DISPLAY vt${TTY} $X_ARGS >/dev/null"

if [ $? -ne 0 ]; then
    echo "Xserver failed, falling back to a text shell" >&2
    exec /bin/bash --login

So simple things like having one thin-client launch firefox and another launch NX player, is easy thru lts.conf and script manipulation - no rebuilding of images required (apart from package installation of course).

Ubuntu LTSP for the Enterprise

Ubuntu LTSP (ltsp-server package) provides a very easy and convenient method for deploying a thin-client environment (see ltsp.org), but is centered primarily around one server being the DHCP/TFTP/NBD and image build server.  Being in an enterprise environment, I needed to be able to split these functions in order to deploy thin clients.  The image below depicts what I needed to achieve. Essentially I wanted to have ubuntu 14.04 images served by older release servers, so my "build" server needs to be 14.04.  Plus I wanted to be able to server multiple custom images off the same server(s).  Note:  I have existing DHCP/TFTP and NBD servers - so this post assumes these are already established - If you do not, then the ltsp-server package has everything you need, and this post is kinda moot.

First, on my build server, I did the following:

apt-get install ltsp-server
ltsp-build-client --dist trusty --arch amd64 --base /opt/ltsp-trusty

This builds your base client system, in my case an amd64 architecture ubuntu 14.04 base.  It takes about 20 minutes depending on your Internet connection and system speed.  Then any customizations can be done via ltsp-chroot:

ltsp-chroot -a amd64 -b /opt/ltsp-trusty --mount-package-cache

(more on what I customized in later post)

And to build the image:

ltsp-update-image --base /opt/ltsp-trusty

So what we have now on the build server, is the image in /opt/ltsp-trusty/images called amd64.img and the needed tftpboot files in /var/lib/tftpboot/ltsp.  In very simple terms, we now need to deploy the image "amd64.img" to the NBD server, and the PXE boot environment to the TFTP server, and then tweak configuration files to point to the right places.

NBD server (Network Block Device)

For every different image you want to deploy, you need nbd-server listening on a different port.  I am using inetd to spawn the nbd-server instances.  Here is my inetd.conf, which is configured for 3 images on ports 2000 thru 2002:

2000  stream  tcp nowait  nobody /usr/sbin/tcpd /usr/sbin/nbdrootd /opt/ltsp/images/i386.img
2001  stream  tcp nowait  nobody /usr/sbin/tcpd /usr/sbin/nbdrootd /opt/ltsp-trusty/images/amd64.img
2002  stream  tcp nowait  nobody /usr/sbin/tcpd /usr/sbin/nbdrootd /opt/ltsp-trusty/images/i386.img

The image files have to be accessible directly, so in my case I copied them from my build server.

TFTP server (Trivial File Transfer Protocol)

For every different image, you need a specific PXE environment defined in /var/lib/tftpboot.  In my case I have 3:


These can be copied as is from the same location on the build server - each environment can have its own lts.conf file.

Now, there is some tweaking required in order for the client to grab the correct NBD image.  You need to add nbdroot={ipaddress}:{port} to the kernel boot parameters for any image served by the non-standard 2000 NBD port.  This is done in the pxelinux.cfg/ltsp file - snippet below.

# This file is regenerated when update-kernels runs.
# Do not edit, see /etc/ltsp/update-kernels.conf instead.
default ltsp-NBD
ontimeout ltsp-NBD

# This file is regenerated when update-kernels runs.
# Do not edit, see /etc/ltsp/update-kernels.conf instead.
label ltsp-NBD
menu label LTSP, using NBD
kernel vmlinuz-3.13.0-37-generic
#append ro initrd=initrd.img-3.13.0-37-generic init=/sbin/init-ltsp quiet splash root=/dev/nbd0
append ro initrd=initrd.img-3.13.0-37-generic init=/sbin/init-ltsp nosplash root=/dev/nbd0 nbdroot=
ipappend 2
DHCP server (Dynamic Host Configuration Protocol)

Your DHCP configuration is key, as it directs the PXE booting client to the correct TFTP server and PXE path.  I use an Etherboot gPXE image served by a local TFTP server to fix issues with older network cards - but you may not need that.  Here is one client config section example:

host webc2 {     hardware ethernet 20:cf:30:6f:0a:9e;
     fixed-address webc2;
     option host-name "webc2";
     if substring (option vendor-class-identifier, 0, 9) = "PXEClient"           {
   if exists user-class and option user-class = "gPXE" {
      filename "/ltsp-trusty/i386/pxelinux.0";
   } else { filename "/tftpboot/undionly.kpxe"; }
} else {
  filename "/ltsp-trusty/i386/nbi.img";}}

The "next-server" portion points it to the TFTP server and PXE file.

You'll probably notice that in my case, my TFTP server and NBD server are on the same server, but its not necessary.

Anyway, in another post I'll discuss an improvement I made to LTSP to allow better scripting via TFTP.