Quick Start
See TAIL OS run on a Linux machine with QEMU — no board, no toolchain, no build. Every command from step 2 on was run against the published image, with Ubuntu 24.04's QEMU 8.2 and with QEMU 10.2, and the output shown is what it printed.
You need a Linux machine with at least 2 CPUs and enough free disk for QEMU and TAIL OS. On Ubuntu 24.04 the QEMU packages below take 580 to 970 MiB, depending on what is already installed; TAIL OS takes about 300 MiB more, or about 600 MiB if you also follow Without the launcher, which keeps its own copy.
QEMU emulates the Raspberry Pi 3's four CPU cores, so the timings on this page are for a
machine with 4 or more CPUs (nproc counts them). With 3 it is only a little slower. With 2,
everything works but runs far slower, as noted below. With 1, the boot stops early; in our
test it had not reached the shell after 15 minutes.
1. Install QEMU
sudo apt-get update
sudo apt-get install -y qemu-system-arm qemu-utils
2. Boot TAIL OS
Download the launcher, then run it:
curl -O https://tail-os.com/downloads/run_tailos_qemu.sh
chmod +x run_tailos_qemu.sh
./run_tailos_qemu.sh
The launcher downloads the kernel and data disk into ~/.cache/tailos/, checks each
against the published SHA256SUMS, and boots them. The first run downloads about 15 MiB,
which unpacks to about 263 MiB in the cache. It shows no progress while it does: after
fetching https://tail-os.com/downloads/tail_disk.img.gz nothing more appears until the
download, unpacking and check are done. Later runs start from the cache, which the launcher
replaces only when a new image is published, so booting again is just ./run_tailos_qemu.sh.
Each run still fetches the small SHA256SUMS to check for a new image, so it needs the
network.
With 4 or more CPUs, boot takes a few seconds. With 2 it takes one to four minutes, and the
log can stop for a minute or more at a time, longest just before the line ending
FAT File System Server initialized; it has not hung.
Before the shell starts, the launcher prints a few lines of its own, then TAIL OS prints a banner
beginning TailOS aarch64 / raspi3b build and about 45 lines of boot log, each tagged [STRT],
[ OK ] or [INFO]. The two [INFO] lines where the wifi and bt expander
did not acknowledge are expected: QEMU's emulated firmware ignores that request. Lines
from different services can arrive out of timestamp order. When the shell starts you see:
TAIL OS Shell (tsh)
Type 'exit' to quit, 'help' for built-in commands.
/$
To leave QEMU at any point, press Ctrl-A, then a lowercase x. QEMU prints QEMU: Terminated
and you are back at your own shell.
3. Look around
With 2 CPUs the shell is slow: typed text can take several seconds to appear, and each
command below takes from several seconds to over ten minutes to finish. Wait for the /$
prompt before typing the next command; text typed while a command is still running appears
twice.
The shell
/$ help
Built-in commands:
exit [code] - Exit the shell (optionally with exit code)
cd [dir] - Change current working directory
pwd - Print current working directory
echo [-n] args - Print arguments ($VAR expanded)
export K=V - Set (and export) an environment variable
cat <file...> - Display file contents
clear - Clear the terminal screen
history - Show command history
mkdir <dir...> - Create directories
touch <file..> - Create empty files
test EXPR - Evaluate a conditional expression
[ EXPR ] - Same as 'test', with a closing ']'
true / false - Yield success / failure status
help - Show this help message
Scripting: 'tsh <file> [args]' runs a script; if/while/for, &&, ||,
';', $?, $1..$@ and '#!/rfs/tsh' shebangs are supported.
External commands are executed as separate processes.
The filesystem
/$ ls /
etc hello.py usr
/$ ls /usr/bin
bluetoothctl isrfat netstat route waitfor
cp kill ping shutdown wcetdump
dhcp list-root-long ps sleep which
fetch logview python time wifi
find ls reboot top
grep mv rm vi
The processes
/$ ps
PID PPID STATE THREADS NAME
0 - LOADING 0 startup
1 - RUNNING 11 kernel
2 - RUNNING 2 uart_pl011
3 - RUNNING 3 log_server
4 - RUNNING 1 pipe_server
5 - RUNNING 2 dwc2_rpi3
6 - RUNNING 4 network
8 - RUNNING 5 gpio_rpi3
9 - RUNNING 1 sd_sdhost_rpi3
10 - RUNNING 1 read_only_file_system_server
11 - RUNNING 1 fat_file_system_server
13 - RUNNING 1 tsh
14 13 RUNNING 1 ps
This is what a microkernel looks like from the inside. The serial driver, the USB and SD
card drivers, both filesystems and the network stack are not part of kernel; each is a
process of its own, listed here beside the shell.
Python
/$ python /hello.py
hello from tailos
python prints nothing for a few seconds before its line, and with 2 CPUs for many minutes
(from 6 to about 15 in our runs, the longer when the two CPUs were separate physical cores).
It has not hung.
The network
/$ ping -c 3 10.0.2.2
PING 10.0.2.2 (10.0.2.2)
reply from 10.0.2.2: seq=0 time=7.199 ms
reply from 10.0.2.2: seq=1 time=3.441 ms
reply from 10.0.2.2: seq=2 time=2.982 ms
--- 10.0.2.2 statistics ---
3 sent, 3 received, 0% lost
10.0.2.2 is QEMU's gateway on the emulated network. The replies travel through the
guest's USB network device, its driver and the network process above. The times differ
on every run. With 2 CPUs a reply can miss ping's 1-second timeout: that probe prints
no reply: seq=N, and the last line counts it as lost.
Without the launcher
If you would rather not run a script, the same steps by hand. Run them in an empty
directory: they keep no cache, the files land there, gunzip stops and asks before
replacing files an earlier run left behind, and it unpacks the disk image to 256 MiB.
mkdir tailos-manual && cd tailos-manual
curl -fsSLO https://tail-os.com/downloads/tail_qemu.rfs.gz
curl -fsSLO https://tail-os.com/downloads/tail_disk.img.gz
curl -fsSLO https://tail-os.com/downloads/SHA256SUMS
gunzip tail_qemu.rfs.gz tail_disk.img.gz
sha256sum -c --ignore-missing SHA256SUMS
qemu-system-aarch64 \
-M raspi3b \
-kernel tail_qemu.rfs \
-serial mon:stdio \
-display none \
-drive file=tail_disk.img,format=raw,if=sd \
-netdev user,id=tailnet0 \
-device usb-net,netdev=tailnet0
-M raspi3b emulates a Raspberry Pi 3B. The last two flags give the guest a USB network
device behind QEMU's user-mode networking, which needs no special permissions on your
machine; without them the guest boots with no network.
Where next
- Periodic framework — Rust: write a node that runs on a fixed period.
- Periodic framework — Python: the same, in Python.