Introduction#
Building a thin client tailored to specific hardware comes down to the following steps:
- Download the full ThinStation repository
- Build a “fat” (full) image
- Boot the thin client using the fat image
- Gather a list of necessary kernel modules and packages for that client
- Adjust the build configs, leaving only what’s essential (including what we just gathered)
- Build a “thin” (lightweight) image
Setting the Stage#
Let me note right away that there’s an alternative method: downloading a prebuilt .iso
image. However, I find that less flexible, so I’ll describe the “proper” approach.
Cloning the Repository#
Basic Git knowledge is recommended for working with ThinStation, as you’ll need a way to save your changes — and it’s easy to get lost in the file hierarchy once the system is unpacked. Cloning is done with:
1git clone --depth 1 git://github.com/Thinstation/thinstation.git -b 5.5-Stable
The 5.5-Stable
is the current branch. Unfortunately, the repository has grown — it used to be just over 2 GB in 2014, and now it’s over 8 GB. That’s why the --depth 1
option is recommended in most guides.
It’s also worth noting that the maintainers recommend cloning as root — I found this out when trying to submit [file ownership fixes][pullrequest-filesmode]. Whether this is “correct” is up for debate, but if you run into permission issues, it might help.
Preparing chroot#
In the root directory of the cloned repository is a bash script named setup-chroot
. You need to run it as root because it mounts system directories. The most useful flag at this stage is -a
— it avoids interactive prompts and installs everything automatically.
Everything else will be done from inside this chroot. Initial setup may take up to an hour (especially on slow disks), but afterward, re-entry is nearly instant.
Fat Image#
This step is a warm-up: we build a full image to boot the hardware and identify required modules and packages. If you manage a mix of hardware or plan future purchases, keep this image handy. You’ll only need to rebuild it after major changes (e.g., a new kernel version).
It’s possible your hardware already has support. Check ts/build/machine
for your platform. If you find it — skip this step.
Build Configuration#
Start by editing ts/build/build.conf
(copy it from build.conf.example
if needed). Uncomment the following lines:
1...
2package lshw # For hardware info
3...
4package xorg7-v4l
5package xorg7-vesa
6package xorg7-vmware
7package xorg7-ati
8package xorg7-nouveau
9package xorg7-openchrome
10package xorg7-intel
11package xorg7-sis
12package extensions
13package extensions-x
14param initrdcmd "gzip"
15param allres true
16param allfirmware true
Why switch compression to gzip
? Because the hwlister.sh
script uses file access times in /lib/firmware
to detect used firmware.
But squashfs
mounts with relatime
, so access times don’t change. gzip
solves this easily. I [reported this issue][issue-squashfs], but haven’t heard back yet.
Building the Image#
Enter the chroot, then run build:
1soar@localhost $ sudo ./setup-chroot
2[root@TS_chroot]/# cd build
3[root@TS_chroot]/build# ./build --allmodules --license ACCEPT --autodl
When done, the boot-images
directory will contain bootable images — ISO, PXE, syslinux. Use whichever suits your needs.
Gathering Data#
After successfully booting the hardware, drop to a shell and run:
1hwlister.sh
This creates:
/firmware.list
/module.list
/package.list
(e.g.,xorg7-*
)/vbe_modes.list
(if usinguvesafb
)
Some files may be absent if no matching data was found.
Also, save the output of lshw
— it may help later when the hardware is no longer at hand.
This script will attempt to upload files to your configured TFTP server — but if, like me, you’ve disabled TFTP write access, just fetch them manually and place them under ts/build/machine/MACHINENAME
.
Thin Image#
This is where you balance functionality and size. Smaller size means faster PXE boot, faster system start, and lower RAM usage. I needed an image for a single use case: an RDP terminal client. Here’s how.
For example, we have a wired office with thin clients that:
- Get IP via DHCP
- Boot via PXE
- Launch a single app — an RDP client
Build Configuration — build.conf
#
- Comment out all
machine
lines except the one you’re using - Keep only essential filesystems —
vfat
,ntfs
; comment outisofs
,udf
,ext*
- Comment all
package xorg7-*
lines if you have a custom machine profile - Comment all
package locale-*
exceptru_RU
and (optionally)en_US
- Enable
package sshd
if you need remote access - Enable
package ccidreader
for smart cards / USB tokens - If you plan to skip window managers/desktops and show just one app (e.g., FreeRDP), enable
package automount
; disablepackage udisks
- To keep it lightweight: enable
package openbox
; disablegtk-*
,icons-*
,fonts-*
Don’t forget to switch back to param initrdcmd "squashfs"
and remove:
Runtime Configuration — thinstation.conf.buildtime
#
This file acts like a bash script that sets environment variables for all boot-time scripts.
For an RDP session, an example config might include:
1# As the user won't have any local UIs, set volumes to max
2AUDIO_LEVEL=100
3MIC_LEVEL=100
4
5# Somewhere to send logs, as we don't have a local storage
6SYSLOG_SERVER=syslog.example.com
7
8# Locale
9LOCALE=ru_RU.UTF8
10TIME_ZONE=Europe/Moscow
11
12# As we don't have a button to eject USB drives, we need this
13USB_STORAGE_SYNC=ON
14DISK_STORAGE_SYNC=ON
15# This will be a directory mounted to the remote desktop
16USB_MOUNT_DIR=/mnt/usb
17# This is my set of parameters suitable for FreeRDP, Windows, FAT32/NTFS and some unusual charsets
18USB_MOUNT_OPTIONS="rw,nosuid,nodev,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,showexec,utf8,flush,errors=remount-ro"
19DISK_MOUNT_OPTIONS="rw,nosuid,nodev,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,showexec,utf8,flush,errors=remount-ro"
20
21# We will also need DHCP
22NET_USE_DHCP=ON
23
24# Desktop
25SESSION_0_TITLE=Desktop
26SESSION_0_TYPE=openbox
27SESSION_0_AUTOSTART=ON
28
29# ... and remote desktop
30SESSION_1_TITLE=RemoteDesktop
31SESSION_1_TYPE=freerdp
32SESSION_1_AUTOSTART=ON
33SESSION_1_FREERDP_SERVER=rdp.example.com
34SESSION_1_FREERDP_OPTIONS="+decorations +fonts +aero ..."
Building the Thin Image#
With config done, build the lightweight image:
1soar@localhost $ sudo ./setup-chroot
2[root@TS_chroot]/# cd build
3[root@TS_chroot]/build# ./build --license ACCEPT --autodl
Depending on your build.conf
, you’ll get bootable images for PXE, CD-ROM, disk, or USB. My setup yielded ~90 MB image size and ~1-minute PXE boot time (from power-on to desktop). From a local disk — even faster.
Other Possibilities#
Everything described above assumes one universal image. But you may want several client variants (e.g., different server addresses). ThinStation supports loading extra configs and modules at boot — this is well documented, so I won’t cover it here.
Useful Notes#
Cleaning the Kitchen#
When switching package versions or compiling binaries, you’ll eventually need to clean up:
- Exit chroot
- Ensure your changes are committed to Git
- Unmount system FS:
umount -R thinstation/*
- Run:
sudo ./setup-chroot -a
- Remove leftovers:
git clean -dx
(deletes all untracked files)
Adding Your Own Packages#
ThinStation (and CRUX Linux) distinguishes between:
- package – installable configuration unit (may just be a config file or list of dependencies)
- port – similar to
.deb
or.rpm
, but only holds a file tree; build/install logic is in side scripts
If you just need to add a few config files — create a package. For binaries — make a port.
Creating a Custom Port#
Add your directory in ts/etc/prt-get.conf
:
1prtdir /ts/ports/yourproject
Create a Pkgfile
:
1name=mdetect-TS
2version=0.5.2.3
3release=1
4source=(http://ftp.de.debian.org/debian/pool/main/m/$name/$name-$version.tar.bz2)
5
6build() {
7 cd $name-$version
8
9 ./configure --prefix=/usr \
10 --exec-prefix=/ \
11 --sysconfdir=/etc \
12 --mandir=/usr/man \
13 --disable-extras
14
15 make
16 make DESTDIR=$PKG install
17}
Build the port:
To fix footprint or checksums:
Then install into your ThinStation build environment:
1prt-get install portname
Creating a Package#
Minimal example:
Add the package in build.conf
. If it depends on your port, include:
1# install
2export PACKAGE=mypackage
3export PORTS=$PACKAGE
4repackage -e
5
6# remove
7export PACKAGE=mypackage
8repackage -c
Updates#
Sometimes the system can become inconsistent (e.g., if .footprint
is outdated or install was interrupted). My go-to recovery steps: