ThinFloe boots a thin client from a USB stick or the network into RAM. Like an ice floe it carries you for a while and is gone at power-off, except for the saved home. Releases before 4.0 were called dbrrg (Docker-Based RamRoot Generator).
Modern diskless Linux system using SquashFS + OverlayFS + ZRAM + Dracut.
Features A/B firmware updates with automatic fallback support.
# Build complete USB image
make
# Or just build rootfs
make rootfs
# Build iPXE network boot loaders
make ipxe
# Clean and rebuild
make clean && make- SquashFS: Compressed read-only root filesystem
- OverlayFS: Writable layer on top of squashfs
- ZRAM: Compressed RAM storage for writes (2GB virtual, ~500MB actual)
- Dracut: Modern modular initramfs system
- SquashFS: 0MB (on USB stick)
- Page cache: 200-800MB (adaptive)
- ZRAM overlay: ~500MB
- Total: 0.7-1.3GB
- SquashFS: ~400MB (in RAM)
- Page cache: 200-800MB (adaptive)
- ZRAM overlay: ~500MB
- Total: 1.1-1.7GB
thinfloe/
├── Makefile # Build system
├── build/ # Build scripts
│ ├── lib/common.sh
│ ├── export-rootfs.sh
│ └── make-bootable-image.sh
├── containers/ # Container definitions
│ ├── ubuntu/Dockerfile
│ ├── image-builder/Dockerfile
│ └── ipxe/Dockerfile
├── configs/ # Configuration files
│ └── syslinux.cfg
├── overlay/ # Rootfs customizations
│ ├── usr/lib/dracut/modules.d/90thinfloe/ # Dracut module
│ └── etc/sysctl.d/99-thinfloe-memory.conf # Memory tuning
└── artifacts/ # Build outputs (gitignored)
├── rootfs/
│ ├── vmlinuz
│ ├── initrd.img
│ ├── ramroot.sqsh
│ ├── ipxe.pxe
│ ├── undionly.kpxe
│ └── ipxe.efi
└── images/
├── thinfloe-usb.img
├── thinfloe-usb.img.zst
└── thinfloe-usb.img.zst.sha256
ramroot=tl/ramroot.sqsh
ramroot=http://server/boot/ramroot.sqsh rd.neednet=1 ip=dhcp
rd.debug rd.shell rd.thinfloe.debug rd.break=pre-mount
Edit Makefile variables:
SQUASHFS_COMP=zstd- Compression algorithm (zstd, xz, lz4)SQUASHFS_COMP_LEVEL=19- Compression level (1-22; higher = smaller download and slower build, reading is equally fast)EFI_PARTITION_SIZE=3000- EFI partition size in MB
# Write compressed image to USB (recommended)
zstd -d < artifacts/images/thinfloe-usb.img.zst | dd of=/dev/sdX bs=1M status=progress conv=fsync
# Or write uncompressed image
dd if=artifacts/images/thinfloe-usb.img of=/dev/sdX bs=1M status=progress conv=fsyncUse the interactive thinfloe-upgrade script on a running system:
sudo thinfloe-upgradeFeatures:
- Drive selection: Choose which USB to upgrade
- Boot USB detection: Safe additive upgrade when running from the target USB
- A/B fallback: Previous version kept as fallback (type
previousat boot menu) - Home copy: A fresh install offers to copy this machine's home onto the new drive (SSH host keys and
wg0.confstay behind, so each machine keeps its own identity)
Upgrade modes:
- Additive (boot USB): Writes new firmware to
tl.new/, keepstl/as fallback - In-place (other USB): Rotates
tl/→tl.old/, installs newtl/ - Full image: Complete rewrite with data backup/restore
After login the session opens a grid of tiles. The shipped tiles start a
terminal, a home backup, thinfloe-upgrade, Settings, Restart and Power off.
Restart and Power off save the home directory first; if the save fails, the
menu asks whether to stay or to go on anyway.
ThinLinc and OxuLnk are not in the image. Tick them in Settings: the image fetches the program from its source, keeps a copy on the boot stick and installs it at every boot, before the session starts. A network-booted machine has no boot stick: it fetches and installs its add-ons about a minute after boot, once the network is up. A new version found by the daily check becomes active at the next boot. Install now and Update now act at once. A source that is not HTTPS and not signed is never fetched automatically; its row says "updates only by hand".
An add-on is installed as root and may write anywhere on the system. The tick list and the tile that names the source are part of the home directory, so whoever can change the saved home of a machine can bring software onto it. Keep the boot stick, and the home archive on the boot server, to yourself.
Add a tile for one machine as a .desktop file in ~/.config/thinfloe/menu/:
[Desktop Entry]
Type=Application
Name=Screenshot
Exec=grim /home/tfuser/screenshot.pngWithout an Icon the tile shows the first letter of Name. The program must
be in the image; a tile whose program is not installed is drawn grey.
Name,Icon,Exec: Label, icon and command of the tile.Terminal=true: Run the command in a terminal window.X-TF-Save-On-Exit=true: Save the home directory when the program exits.
The files are saved with the home directory. A file with the name of a
shipped tile (for example 10-thinlinc.desktop) rewords that tile's Name,
Comment and Icon; its other keys are ignored. thinfloe-menu --check run
from a VT explains why a tile is grey.
# Build iPXE boot loaders
make ipxe
# Copy files to TFTP/HTTP server
cp artifacts/rootfs/ipxe.pxe /var/lib/tftpboot/
cp artifacts/rootfs/ipxe.efi /var/lib/tftpboot/
cp artifacts/rootfs/vmlinuz artifacts/rootfs/initrd.img /var/www/html/boot/
cp artifacts/rootfs/ramroot.sqsh /var/www/html/boot/
# Configure DHCP to serve iPXE, then chainload to kernel with:
ramroot=http://your-server/boot/ramroot.sqshSee MIGRATION.md for detailed migration guide.
# Check podman is working
podman --version
# Clean and rebuild
make clean && make# Use debug mode in syslinux.cfg
rd.debug rd.shell rd.thinfloe.debug
# Check logs after boot
cat /var/log/dracut-boot.log
cat /var/log/thinfloe/*Edit overlay/etc/sysctl.d/99-thinfloe-memory.conf:
- Increase
vm.vfs_cache_pressureto 200 (more aggressive cache eviction) - Decrease ZRAM size in dracut module
# Rebuild initramfs quickly
dracut --force --add thinfloe
# Test in QEMU
qemu-system-x86_64 -m 4G -enable-kvm \
-drive file=artifacts/images/thinfloe-usb.img,format=rawEdit files in overlay/usr/lib/dracut/modules.d/90thinfloe/:
parse-thinfloe.sh- Command line parsingfinalize-upgrade.sh- Activate pending tl.new/ upgradesmount-squashfs.sh- SquashFS mountingsetup-overlay.sh- Overlay setup
Rebuild with make rootfs
The system supports A/B firmware updates with automatic fallback:
EFI-SYSTEM partition:
├── tl/ # Current firmware (or tl.new/ after additive upgrade)
├── tl.old/ # Previous firmware (fallback)
├── config/ # Persistent configuration
└── home.tar.gz # User home backup
current- Boot the active firmware versionprevious- Boot the previous version (fallback)console- Text-only bootdebug- Debug shell
If a new firmware version fails to boot:
- Reboot and press any key at the boot prompt
- Type
previousand press Enter - System boots from the previous known-good version