FreeLinX

A Linux distribution with a NetBSD userland, the musl C library and an LLVM toolchain. It contains no GNU software.

This document covers FreeLinX base 1.3.1: what it is, how to install and run it, how it works inside, and how to build it from source.

Contents:

  1. About
  2. How FreeLinX works
  3. Getting FreeLinX
  4. The live system
  5. Installing with xsetup
  6. Disk layout and boot configuration
  7. The boot process
  8. Services, devices and consoles
  9. Users and privileges
  10. Networking
  11. Packages and xpkg
  12. X and Openbox
  13. The no-GNU rule
  14. Upgrading
  15. Building from source
  16. Tests
  17. Command reference
  18. Troubleshooting
  19. Repositories
  20. Supporting FreeLinX

1. About

FreeLinX is an independent distribution. It is not derived from Debian, Alpine or any other distribution. It combines:

componentFreeLinX usesinstead of
kernelLinux 6.18 LTS-
C librarymusl 1.2.5glibc
compiler and linkerclang, LLD, compiler-rt, libc++ (LLVM)GCC, binutils ld, libgcc, libstdc++
command line toolsNetBSD userland (ls, cp, sh, awk, grep, sed, tar, ...)GNU coreutils, bash, gawk, grep, sed, tar
initrunitsystemd, sysvinit
device managermdevdudev
interactive shellmkshbash
terminal librarynetbsd-cursesncurses
package managerxpkg (written for FreeLinX)-
installerxsetup (written for FreeLinX)-
boot loaderLimine 12.9GRUB

Software that is GPL-licensed but not part of the GNU project, such as the Linux kernel, is allowed. What is excluded is GNU code: glibc, GCC and its runtime, and GNU libraries and tools. See section 13 for how this is checked.

Editions

FreeLinX base is a console system with a shell, network tools, an editor, a C compiler and the package manager. Everything else is installed from the package repository. This document is about base.

FreeLinX desktop (repository FreeLinX-desk) is built from the same packages and boots into an Openbox desktop with a browser based on Firefox ESR. Base can get close to it by installing packages (see section 12).

What base contains

areasoftware
systemrunit, mdevd, dbus, doas, toybox (getty, login, ps, top, pgrep, free, uptime)
shellsmksh R59 (login shell), NetBSD sh (/bin/sh, for scripts)
networkdhcpcd, wpa_supplicant, flxwifi, iproute2 (ip), OpenSSH 10.5 (ssh, sshd, scp, sftp), curl, ntpd
files and diskse2fsprogs, dosfstools, flxpart, lsof
editors and toolsvim (also vi), less, tmux, htop, nnn, git, fastfetch
documentationmandoc (man, apropos, whatis), about 200 NetBSD manual pages
developmenttcc (as cc) with the musl and Linux headers
firmwarelinux-firmware: Intel, AMD, Atheros, Realtek, MediaTek, Broadcom and others

X11, GTK, Mesa and fonts are not on the image.

Requirements

2. How FreeLinX works

This section is an overview of the whole system: its layers, what happens from power-on to the login prompt, which processes run, where files are and how programs find their libraries. The later sections describe each part in detail.

Layers

+-------------------------------------------------------------+
| your programs: st, firefox, vim, git ...    (from xpkg)     |
+-------------------------------------------------------------+
| services: runit, mdevd, dbus, dhcpcd, ntpd, sshd, flxwifi   |
| tools:    NetBSD userland, toybox, mksh, xpkg, xsetup       |
+-------------------------------------------------------------+
| libraries: libc++, openssl, sqlite, zlib, netbsd-curses ... |
+-------------------------------------------------------------+
| musl: the C library and the dynamic loader                  |
+-------------------------------------------------------------+
| Linux kernel: drivers, filesystems, network, processes      |
+-------------------------------------------------------------+
| firmware (BIOS or UEFI) and Limine                          |
+-------------------------------------------------------------+

Every program above the kernel was compiled with clang against musl. Each layer uses only the layers below it. There is no compatibility layer for glibc: a program built for another distribution's glibc does not run on FreeLinX and has to be rebuilt.

From power-on to the prompt

  1. Firmware. BIOS or UEFI finds the boot loader: Limine's BOOTX64.EFI on the EFI system partition, or Limine's BIOS stage in the BIOS boot partition.
  2. Limine. Reads limine.conf, shows the menu, loads the kernel (and, on the live medium, the small initramfs) and passes the kernel command line.
  3. Kernel. Starts the processors, memory management and the built-in drivers. On an installed system it finds the root partition by its PARTUUID and mounts it read-only. On the live medium it unpacks the initramfs and runs flxlive, which mounts the squashfs image from the medium under a RAM overlay and makes that the root.
  4. /init. A shell script, running as process 1. It mounts /proc, /sys, /dev and /run, checks and remounts the root filesystem read-write, starts mdevd, loads drivers, mounts /home, starts the network and sets the host name.
  5. runsvdir. /init replaces itself with runsvdir, which stays process 1. It starts one runsv supervisor for every directory in /var/service.
  6. Services. Each runsv starts its service and restarts it if it exits. The shell service runs flxconsole, which puts getty and login (or, on the live medium, a root shell) on the consoles.
  7. Login. login checks the password against /etc/shadow, sets the user and group ids and starts the user's shell, mksh, which reads /etc/profile.

On a virtual machine with KVM this takes a few seconds.

Processes on a running system

PID 1  runsvdir -P /var/service
         runsv shell  -- flxconsole
                           getty ttyS0 / tty1 / tty2 / tty3
                           login -- mksh          (after you log in)
         runsv dbus   -- dbus-daemon --system
         runsv ntpd   -- ntpd
         runsv flxwifi
         runsv sshd   -- sshd                     (if enabled)
       mdevd                                      (started by /init)
       dhcpcd                                     (started by /init)

That is the whole system: about twenty processes and 55 MB of RAM. If a supervised service dies, runsv starts it again. If a login session ends, flxconsole starts a new getty on that console.

Filesystem layout

pathcontents
/initthe boot script, process 1 until it starts runsvdir
/bin, /sbinessential commands: the NetBSD tools, sh, mksh, runit, mdevd, e2fsprogs, the flx* system scripts
/libthe musl library and loader (ld-musl-x86_64.so.1), kernel modules in /lib/modules, firmware in /lib/firmware
/usr/bin, /usr/libeverything else: programs and shared libraries from packages
/usr/includeC headers for musl and Linux (used by cc)
/usr/sharemanual pages, time zones, fonts, terminal descriptions, data
/etcconfiguration, all plain text
/etc/svcavailable but disabled services
/var/serviceenabled services (directories or links into /etc/svc)
/var/lib/xpkgthe package database
/var/cache/xpkgdownloaded package archives
/var/loglog files: mdevd.log, ntpd.log, sshd.log and others
/runtmpfs: sockets and pid files, empty at every boot
/tmptmpfs: temporary files, empty at every boot
/homeuser directories, on their own partition
/rootroot's home directory
/media/flxthe live medium, read-only, when one is attached
/boota copy of the kernel; the boot loader reads the one on the ESP

How a program runs

Every dynamically linked program names /lib/ld-musl-x86_64.so.1 as its interpreter. When the kernel starts the program it first loads that file, which is musl itself: in musl the C library and the dynamic loader are the same file. The loader then finds the other libraries the program needs in the directories listed in /etc/ld-musl-x86_64.path:

/lib
/usr/local/lib
/usr/lib

There is no ldconfig and no library cache: a library is found as soon as it is in one of these directories. ldd PROGRAM shows what a program loads.

Configuration

Everything is configured with text files in /etc, edited with any editor. There is no registry, no configuration daemon and no binary log. Changes take effect when the program reads the file again: restart the service with sv restart NAME, or log in again for /etc/profile. xsetup only writes these files; anything it does can also be done by hand.

filewhat it sets
/etc/hostname, /etc/hostshost name
/etc/passwd, /etc/group, /etc/shadowusers, groups, password hashes
/etc/doas.confwho may run commands as root
/etc/profileenvironment for login shells: PATH, TZ, prompt, SVDIR
/etc/dhcpcd.conf, /etc/resolv.confnetwork addresses and name servers
/etc/wpa_supplicant/flxwifi.confsaved Wi-Fi networks
/etc/mdev.confdevice permissions and hotplug actions
/etc/fstab, /etc/flx-diskfilesystems and the partitions of this installation
/etc/localtime, /etc/TZtime zone
/etc/xpkg/package repositories and trusted keys
/etc/ssh/SSH client and server
/etc/os-releasethe release name and version

Where software comes from

The ISO contains the base system. Everything else comes from the package repository through xpkg: X, window managers, browsers, media players, compilers and libraries. The base system itself is also made of packages from the same repository, plus the files in the src repository (the boot script, configuration and the flx* scripts). An installed system can be brought up to date in two ways: xpkg upgrade for packages, and flxupgrade from a newer ISO for the base files.

Design rules

3. Getting FreeLinX

Releases are on GitHub. Each release has the ISO image and a sha256 file.

curl -LO https://github.com/FreeLinX/FreeLinX-base/releases/latest/download/freelinx-base-x86_64.iso
curl -LO https://github.com/FreeLinX/FreeLinX-base/releases/latest/download/freelinx-base-x86_64.iso.sha256
sha256sum -c freelinx-base-x86_64.iso.sha256

USB stick

The ISO is a hybrid image: it can be written to a USB stick as it is.

lsblk                       # find the stick, for example /dev/sdb
dd if=freelinx-base-x86_64.iso of=/dev/sdX bs=4M conv=fsync

Check the device name carefully: dd overwrites the whole device. Then boot the computer from the stick. Both legacy BIOS and UEFI boot are supported.

QEMU

Create a disk image once, then boot the ISO with the disk attached:

qemu-img create -f qcow2 flx.qcow2 20G
qemu-system-x86_64 -enable-kvm -cpu host -m 4096 -smp 2 \
  -drive file=flx.qcow2,if=virtio,format=qcow2 \
  -cdrom freelinx-base-x86_64.iso -boot d \
  -device VGA,xres=1600,yres=900 \
  -nic user,model=virtio-net-pci

After installation, start the same command without -cdrom and -boot d to boot from the disk. Running qemu-img create again empties the disk.

-device VGA,xres=...,yres=... tells the guest the preferred screen size; both the text console and X use it. With plain -vga std the guest gets a small default mode.

For UEFI, add OVMF:

cp /usr/share/OVMF/OVMF_VARS_4M.fd vars.fd
qemu-system-x86_64 -enable-kvm -cpu host -m 4096 -smp 2 -machine q35 \
  -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
  -drive if=pflash,format=raw,file=vars.fd \
  -drive file=flx.qcow2,if=virtio,format=qcow2 \
  -cdrom freelinx-base-x86_64.iso -boot d \
  -device VGA,xres=1600,yres=900 -nic user,model=virtio-net-pci

Without -enable-kvm QEMU emulates the CPU and everything is 10 to 20 times slower.

4. The live system

When the ISO boots, Limine shows a menu with two entries, "FreeLinX 1.3.1 base (installer: xsetup)" and "Rescue shell". The first one starts after five seconds. The kernel messages are shown, then the screen is cleared and the banner appears with a root prompt.

The live system gives a root shell without a password on tty1, tty2, tty3 (switch with Alt+F1 to Alt+F3) and on the serial port ttyS0. There is also a user live, which may use doas without a password; it is removed during installation.

The system files are read from the medium. Changes are stored in RAM and are lost on reboot. The writable layer can use up to 75% of RAM. A large package like Firefox does not fit in a live session on a small machine; install to disk first.

The medium itself is mounted read-only at /media/flx.

Network: on wired interfaces dhcpcd starts at boot. Wi-Fi is configured with flxwifi (see section 10).

5. Installing with xsetup

Run xsetup as root. It runs thirteen steps in order, each one asking its questions and writing its files. A finished step is recorded in /etc/xsetup.state; if the installer stops, running xsetup again continues with the first unfinished step.

xsetup                  # run all steps that are not done
xsetup --list           # list the steps
xsetup --status         # show which steps are done
xsetup --reset STEP     # mark a step as not done
xsetup setup-hostname   # run one step

The steps

setup-keymap
Asks for a keyboard layout and records it in /etc/conf.d/loadkmap.conf. It is not applied to the running text console: current Linux kernels no longer allow programs to change the console keymap. X uses its own layout setting.
setup-hostname
Asks for the host name, sets it and writes /etc/hostname and /etc/hosts. Names follow RFC 1123: letters, digits, dots and dashes, labels of 1 to 63 characters, at most 253 in total, no label starting or ending with a dash, no leading or trailing dot.
setup-interfaces
For each wired interface: DHCP, a static address, or leave it alone. Static settings (address, gateway, name server) go into a marked block in /etc/dhcpcd.conf. If there is a wireless interface it offers to set up Wi-Fi and saves the network in /etc/wpa_supplicant/flxwifi.conf (mode 0600); the key is stored as the 64-digit PSK computed by wpa_passphrase, and for WPA3 also as sae_password when the passphrase can be quoted.
setup-passwd
Asks for the root password twice. An empty password is refused; one shorter than six characters needs confirmation. The hash is written to /etc/shadow by flxpasswd, which uses the C library's crypt(3).
setup-timezone
Asks for a region and a city from /usr/share/zoneinfo, copies the zone to /etc/localtime and writes the name to /etc/timezone and /etc/TZ. /etc/profile exports TZ from /etc/TZ.
setup-proxy
Optional HTTP proxy. Writes /etc/proxy.conf (mode 0600) and /etc/profile.d/proxy.sh, which exports http_proxy and https_proxy. The proxy password is read without echo.
setup-ntp
Enables or disables ntpd by linking /etc/svc/ntpd into /var/service. Correct time matters: TLS certificate checks fail with a wrong clock.
setup-apkrepos
Sets the package repository URL in /etc/xpkg/repos.conf and runs xpkg update. The default is the official signed repository. A plain http:// URL is accepted only after a warning.
setup-user
Creates a user. Asks for the name, whether the user may run commands as root with doas, and the password twice. Names must start with a letter or underscore, contain letters, digits, dash and underscore, and be at most 32 characters; upper case is folded to lower case. The user gets uid 1000 or the next free one, a group of the same name, the groups audio, video, input, storage and users, and wheel only if doas was allowed. The home directory is /home/NAME with mode 0700, and the shell is the one named in /etc/flx-shell (mksh).
setup-sshd
Enables or disables the OpenSSH server. When enabled it generates host keys (ed25519 and rsa) and links /etc/svc/openssh to /var/service/sshd.
setup-disk
Asks whether to keep running from RAM or install to a disk. For an install it lists the disks with their size and model, asks which one, refuses a disk that has a mounted filesystem, and erases it only after you type yes. It then partitions, formats, copies the system and installs the boot loader. Details are in the next section.
setup-lbu, setup-apkcache
Report where changes and the package cache are kept. On an installed system both are on the root partition; they change nothing.

What setup-disk does

  1. Writes a GPT with flxpart --create-standard: ESP, BIOS boot, root and home partitions.
  2. Formats the ESP as FAT32 (label FLX_BOOT), root as ext4 (label FLX_ROOT) and home as ext4 (label FLX_HOME).
  3. Rewrites the banner in /etc/motd and /etc/issue so it no longer says "Live system".
  4. Copies the running system to the root partition: every top-level directory except /proc, /sys, /dev, /run, /tmp, /mnt, /media and /home.
  5. On the copy: removes the live user and its doas rule, creates /etc/flx-installed, writes /etc/flx-disk and /etc/fstab.
  6. Copies the kernel to the ESP, installs Limine for UEFI (EFI/BOOT/BOOTX64.EFI) and BIOS (limine bios-install into the BIOS boot partition), and writes limine.conf with the root partition's PARTUUID.
  7. Copies the home directories of users with uid 1000 and above to the home partition.

When xsetup finishes, power off, remove the medium and boot from the disk.

6. Disk layout and boot configuration

#partitionfilesystem, labelsizemounted at
1EFI systemFAT32, FLX_BOOT1 GiBnot mounted
2BIOS bootnone1 MiB-
3rootext4, FLX_ROOTsee below/
4homeext4, FLX_HOMEthe rest of the disk/home

Root partition size: on disks of 20 GiB or more, 30% of the disk, at least 6 GiB and at most 64 GiB; on smaller disks 40%, at least 3 GiB. The minimum disk size is 8 GiB.

/etc/flx-disk

Names the two ext4 filesystems by UUID. /init mounts /home from this pin, so another disk with a partition labelled FLX_HOME is never mounted by mistake.

FLX_ROOT_UUID=2db63e9e-...
FLX_HOME_UUID=792ae350-...

/etc/fstab

UUID=2db63e9e-...   /       ext4   defaults,noatime         0 1
UUID=792ae350-...   /home   ext4   defaults,noatime         0 2
tmpfs               /tmp    tmpfs  mode=1777,nosuid,nodev   0 0

The root filesystem is mounted by the kernel and /home and /tmp by /init; fstab documents them.

limine.conf

On the ESP (also copied to boot/ and EFI/BOOT/):

timeout: 3
serial: yes
textmode: yes
interface_branding: FreeLinX 1.3.1

/FreeLinX 1.3.1
    protocol: linux
    kernel_path: boot():/boot/bzImage
    cmdline: root=PARTUUID=... rootfstype=ext4 rootwait ro init=/init console=ttyS0,115200 console=tty0

/Rescue shell
    protocol: linux
    kernel_path: boot():/boot/bzImage
    cmdline: root=PARTUUID=... rootfstype=ext4 rootwait ro init=/init console=ttyS0,115200 console=tty0 flx.rescue=1

The PARTUUID is the GPT partition GUID of the root partition. setup-disk reads it from the partition table directly, because the system's blkid does not report it. console=tty0 is last, so /dev/console is the screen; kernel messages also go to the serial port.

7. The boot process

Live medium

Limine --> kernel + initramfs --> flxlive --> overlay root --> /init --> runsvdir

The ISO contains:

boot/bzImagethe kernel
boot/initramfs.img.gzabout 290 KB: flxlive as /init and a static /bin/sh
boot/root.sfsthe system, squashfs compressed with zstd, about 290 MB
boot/limine/, EFI/BOOT/Limine for BIOS and UEFI

flxlive is a 46 KB static C program (scripts/flxlive.c). It is written in C because switching to a new root needs MS_MOVE mounts, chroot and a loop device, and the userland has no switch_root, chroot or losetup commands. It does the following:

  1. mounts /proc, /sys, /dev (devtmpfs) and a tmpfs on /run, and opens /dev/console for its output;
  2. scans every block device for an ISO 9660 volume labelled FREELINX_LIVE (the label can be changed with flx.medium=LABEL on the kernel command line), waiting up to 30 seconds for slow USB and CD drives;
  3. mounts the medium read-only;
  4. attaches boot/root.sfs to a free loop device, read-only;
  5. mounts the squashfs read-only as the lower layer;
  6. mounts a tmpfs limited to 75% of RAM for the upper and work directories;
  7. mounts an overlay of the two on /newroot;
  8. moves the medium to /newroot/media/flx and /dev to /newroot/dev;
  9. makes /newroot the root (MS_MOVE and chroot) and executes /init.

If any step fails, flxlive prints which one and why and starts /bin/sh.

Installed system

Limine --> kernel mounts FLX_ROOT read-only --> /init --> runsvdir

There is no initramfs. The kernel has the disk drivers (virtio, NVMe, AHCI, SCSI disk, USB storage and UAS), the EFI and MBR partition parsers and ext4 built in, so it can find the root partition by PARTUUID and mount it by itself. rootwait makes it wait for slow devices.

/init

/init is a shell script and runs as process 1, both on the live medium and on an installed system. In order it:

  1. mounts /proc, /sys, /dev, a tmpfs on /run and /dev/pts, and creates the /dev/fd, /dev/stdin, /dev/stdout and /dev/stderr links;
  2. if the root filesystem is on a disk (not the live overlay): runs e2fsck -p on it. Exit code 1 means errors were fixed; 2 or 3 means the root filesystem was changed and the machine reboots; 4 or more means errors that need manual repair, and a shell is started. Then it remounts / read-write and mounts a tmpfs on /tmp;
  3. creates /var/log;
  4. starts mdevd (logging to /var/log/mdevd.log) and runs mdevd-coldplug so devices that already exist get their nodes and modules;
  5. loads drivers for detected hardware with flxdriver;
  6. mounts /home from the FLX_HOME UUID in /etc/flx-disk (or, without a pin, from the label FLX_HOME), and mounts the live medium read-only at /media/flx if one is attached;
  7. brings up the loopback interface and starts dhcpcd on eth0;
  8. sets the host name from /etc/hostname;
  9. if the kernel command line contains flx.rescue=1, starts a root shell on the console instead of the services;
  10. otherwise executes runsvdir -P /var/service.

Shutdown

poweroff and reboot run flx-shutdown. It stops every service with sv down, sends TERM and then KILL to all processes, syncs, unmounts every disk filesystem in reverse order (or remounts it read-only if it is busy), remounts / read-only, and finally triggers the kernel's emergency sync, unmount and power-off or reboot through /proc/sysrq-trigger.

8. Services, devices and consoles

runit

Each directory in /var/service is a service supervised by runsv. A service directory contains an executable run script that starts the program in the foreground.

servicewhat it runs
shellflxconsole: shells or logins on the consoles
dbusthe system message bus, on /run/dbus/system_bus_socket
ntpdtime synchronisation, logs to /var/log/ntpd.log (link to /etc/svc/ntpd)
flxwificonnects to saved Wi-Fi networks at boot
sshdOpenSSH server, if enabled (link to /etc/svc/openssh), logs to /var/log/sshd.log
sv status /var/service/*     # state of all services
sv restart sshd              # restart one (SVDIR=/var/service is set in /etc/profile)
sv down ntpd                 # stop until the next boot
ln -s /etc/svc/openssh /var/service/sshd    # enable
rm /var/service/sshd                        # disable

Devices

The kernel creates device nodes in devtmpfs. mdevd receives kernel events and applies the rules in /etc/mdev.conf: it sets owners and modes, loads modules and calls helper scripts. Some rules:

null      0:0 0666
zero      0:0 0666
random    0:0 0666
urandom   0:0 0666
tty       0:5 0666
console   0:5 0600
sd[a-z][0-9]*            0:6 0660 */sbin/flxautomount
nvme[0-9]*n[0-9]*p[0-9]* 0:6 0660 */sbin/flxautomount
sr[0-9]*                 0:6 0660 */sbin/flxautomount

flxautomount mounts removable media when they appear. It does nothing while the installer is partitioning a disk.

Consoles

The shell service runs /sbin/flxconsole, which looks after /dev/ttyS0, /dev/tty1, /dev/tty2 and /dev/tty3:

TERM is linux on the screen and vt100 on a serial line. The prompt shows user, host and the current directory.

9. Users and privileges

doas

Members of the group wheel can run commands as root with doas, after entering their own password (remembered for a while with persist). /etc/doas.conf:

permit persist :wheel
permit nopass :wheel cmd /sbin/reboot
permit nopass :wheel cmd /sbin/poweroff
permit nopass :wheel cmd /usr/bin/zzz

There is no sudo.

Passwords and accounts

A forgotten root password can be reset from the "Rescue shell" boot entry: it gives a root shell with the disk mounted read-write; run passwd root.

10. Networking

Wired

dhcpcd runs from /init on eth0. Static settings made with setup-interfaces are kept in a block in /etc/dhcpcd.conf:

# --- xsetup: begin ---
interface eth0
static ip_address=192.168.1.10/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1
# --- xsetup: end ---

dhcpcd writes /etc/resolv.conf. ip addr, ip route and ifconfig show the state.

Wi-Fi

flxwifi scan                               # list networks
FLXWIFI_PASS='passphrase' flxwifi connect "MyNet"
flxwifi status
flxwifi list                               # saved networks
flxwifi disconnect

flxwifi runs wpa_supplicant (nl80211) and dhcpcd. Passing the passphrase in FLXWIFI_PASS keeps it out of the process list. Saved networks are in /etc/wpa_supplicant/flxwifi.conf and are joined at boot by the flxwifi service. The firmware for common Intel, Atheros, Realtek and MediaTek cards is included, and the regulatory database is installed.

flxnet is a small text menu for wired and wireless setup.

SSH

Enable the server with xsetup setup-sshd or by linking /etc/svc/openssh into /var/service. Configuration is in /etc/ssh/sshd_config; host keys are created on first start.

11. Packages and xpkg

Usage

xpkg update                 # download the repository index
xpkg search WORD            # search names and descriptions
xpkg show NAME              # details of a package in the repository
xpkg install NAME...        # install with dependencies
xpkg install ./file.xpkg    # install a local archive
xpkg remove NAME...
xpkg autoremove             # remove dependencies no longer needed
xpkg upgrade                # update the index and upgrade everything
xpkg outdated               # list packages with newer versions
xpkg list -e                # packages you installed explicitly
xpkg info NAME              # details of an installed package
xpkg files NAME             # files of an installed package
xpkg owns /usr/bin/st       # which package owns a file
xpkg verify                 # check installed files against the database
xpkg clean                  # delete downloaded archives

Common options: -n shows what would happen without doing it, -q and -v change the output, --root DIR operates on a system mounted at DIR.

Files and directories

/etc/xpkg/repos.confrepository URLs, one per line, highest priority first
/etc/xpkg/keys/trusted public keys (Ed25519)
/var/lib/xpkg/xpkg.dbinstalled packages and their files (SQLite)
/var/cache/xpkg/downloaded indexes and archives

Package format

A .xpkg file is a gzip-compressed ustar archive:

pkg-info        NAME, VERSION, DESCRIPTION, ARCH, DEPENDS (key=value lines)
post-install    optional sh script, run after install and upgrade
pre-remove      optional sh script, run before removal
files/...       the files, relative to /

Example pkg-info:

NAME=xinit
VERSION=1.4.4-3
DESCRIPTION=xinit, startx and the default X session
ARCH=x86_64
DEPENDS=libX11,musl

xpkg reads the archive itself; it does not call tar, gzip or curl.

Repository and verification

A repository is a directory served over HTTPS with index.json, index.json.sig and the archives. For each package the index lists version, description, dependencies, file name, size and sha256.

The official repository is the Hugging Face dataset FreeLinX/packages with 428 packages. Every package passes check-nognu before it is published.

Configuration files

Files under /etc are treated as configuration. If a package update brings a new version of a file you have changed, your file is kept and the new one is written next to it as FILE.xpkgnew.

12. X and Openbox

xpkg install xorg xinit openbox
startx

The xorg package depends on the X server, the libinput and evdev input drivers, the fbdev video driver, Mesa, xkbcomp and the XKB layout data. openbox depends on fonts (DejaVu) and the st terminal. The modesetting video driver built into the server is used on most hardware and in QEMU.

The default session

Without ~/.xinitrc, startx runs /etc/X11/xinit/xinitrc. It picks the first installed terminal (st, urxvt or xterm) and window manager (openbox, dwm or twm). With openbox it runs exec openbox --startup st, so the terminal starts after openbox has taken over the screen. A terminal started at the same moment as the window manager can be lost and never appear.

Your own ~/.xinitrc should therefore end like this:

exec openbox --startup st

Openbox

Right click on the desktop opens the root menu: Terminal, Reconfigure, Restart, Exit. The defaults are in /etc/xdg/openbox/rc.xml and /etc/xdg/openbox/menu.xml; copy them to ~/.config/openbox/ to change them.

Screen size

xpkg install xrandr
xrandr                     # list modes
xrandr -s 1920x1080

To make it permanent, put the xrandr line in ~/.xinitrc before the exec line.

More software

Examples from the repository: firefox, mpv, feh, nsxiv, mupdf, dillo, xfe, tint2, dwm, dmenu, urxvt, ffmpeg. Firefox needs about 500 MB of space. The first start of st or another program that uses fontconfig takes a few seconds while the font cache is built.

13. The no-GNU rule

Every image and every package is checked by check-nognu.sh before release. It examines every ELF file and fails if any of these is true:

The last check exists because GNU code compiled with clang and linked statically leaves no GCC marker and no library dependency.

14. Upgrading

Packages

doas xpkg upgrade

This updates everything installed from the repository, including the kernel package.

The base system

Boot the ISO of the new release on the installed machine and run flxupgrade as root. It:

  1. finds the installed system by its FLX_BOOT and FLX_ROOT partitions (or the disk given as argument);
  2. checks the root filesystem with e2fsck and mounts it;
  3. copies /usr, /bin, /sbin, /lib and /init from the live system over the installed ones, so packages you installed into /usr stay;
  4. adds files in /etc that the new release has and the installed system does not have, without changing existing files, and updates /etc/os-release;
  5. copies the new kernel to the ESP and to /boot, installs the new Limine for UEFI and BIOS and writes a new limine.conf.

Users, passwords, settings, packages and /home are kept. Afterwards run doas xpkg upgrade.

Releases 1.0.13 to 1.1.x installed a different layout: the system ran from an image in RAM and kept /usr, /etc, /var and the rest on a partition labelled FLX_SYS. flxupgrade converts such a disk: FLX_SYS becomes the root partition (it is relabelled FLX_ROOT and gets the missing top-level directories), and the RAM image is removed from the ESP.

15. Building from source

The ISO can be built on any x86_64 Linux system without root.

Host packages

apt install git curl python3 openssl xorriso squashfs-tools cpio xz-utils binutils
apt install qemu-system-x86 qemu-utils ovmf        # only for the QEMU tests

Sources

Clone the repositories next to each other and unpack the toolchain release:

mkdir FreeLinX && cd FreeLinX
for r in FreeLinX-base src ports drivers; do
    git clone https://github.com/FreeLinX/$r.git
done
curl -LO https://github.com/FreeLinX/toolchain/releases/download/v1.0.0/toolchain.tar.gz
tar -xzf toolchain.tar.gz
FreeLinX/
  FreeLinX-base/   build scripts, installer, flxlive, tests
  src/             root filesystem
  ports/           console ports (built packages in ports/packages)
  drivers/         Limine binaries (bootloader/limine-binary)
  toolchain/       clang, LLD, musl sysroot

Build

cd FreeLinX-base
sh build-base.sh

Output: out/freelinx-base-x86_64.iso and its sha256 file. The build takes a few minutes and downloads about 150 MB.

What build-base.sh does

  1. Finds a host xpkg. If the machine has none, scripts/host-xpkg.sh downloads xpkg and its libraries from the repository, verifies the index signature (with openssl or python3-cryptography) and the archive checksums, and runs xpkg through the downloaded musl loader.
  2. Runs scripts/mkrootfs.sh to make the root filesystem (below).
  3. Uses the kernel from the linux package (/usr/lib/linux/bzImage-*), so kernel and modules match. Exactly one kernel version may be present in /lib/modules.
  4. Sets file modes that git does not keep (/etc/shadow 0600, setuid on su, newgrp, doas), writes /etc/os-release and the banner.
  5. Runs check-nognu again on the final tree.
  6. Packs the tree into boot/root.sfs with mksquashfs (zstd, 1 MiB blocks, all files owned by root).
  7. Compiles scripts/flxlive.c statically with the musl toolchain, checks it with check-nognu, and packs it with a static sh as the initramfs.
  8. Writes the ISO with xorriso (ISO 9660 with Rock Ridge, label FREELINX_LIVE, El Torito for BIOS and UEFI) and runs limine bios-install on it so it also boots from a USB stick.

What mkrootfs.sh does

  1. Copies src/rootfs. Refuses to continue if that tree has uncommitted changes, unless ALLOW_DIRTY=1.
  2. Installs these packages by name from the signed repository: ca-certificates, dbus, expat, flxnet, libcxx, libedit, libelf, libffi, libmd, libnl, libudev-zero, libxml2, linux, linux-firmware, musl, musl-dev, netbsd-curses, nnn, openssl, pcre2, sqlite, toybox, tzdata, wpa_supplicant, xpkg, zlib.
  3. Removes desktop leftovers (the desktop installer wrapper, X configuration, themes).
  4. Installs mandoc, less, iproute2, lsof, mksh, stty and tcc from ports/packages, builds the man page index, compiles the file magic database from file-5.46 (downloaded from the FreeLinX source mirror and checked if not present locally), and adds the repository key and the xsetup installer.
  5. Trims what a console system does not use: static libraries no package owns, vim's test files, and similar.
  6. Checks: every ELF file's libraries are present, no program links against X or GTK, check-nognu passes, and flxconsole gives an installed system a login rather than a shell.

Variables

SERIAL=1kernel console also on ttyS0; needed by the QEMU tests
OUT=file.isooutput path
ALLOW_DIRTY=1build from a src tree with uncommitted changes
XPKGcommand that runs a host xpkg
SYSROOTmusl sysroot (default ../toolchain/x86_64-linux-musl)
LIVECCC compiler command for flxlive (default ../toolchain/bin/clang with the musl target)
REPOpackage repository URL
LIMINE_DIRLimine files (default ../drivers/bootloader/limine-binary)
FLXSRCsrc checkout (default ../src)
FLX_HW_TARBALLextra firmware tarball
BASE_FROM_DESKTOP=1build from a built FreeLinX-desk tree instead of src

Packages

The libraries and programs in the repository are built by the desktop stack (FreeLinX-desk/stack/build-stack.sh and package-stack.sh) and by the ports tree, with the same clang and musl toolchain, then signed and published with xpkg/tools/publish-repo.sh, which runs check-nognu on every archive first.

16. Tests

suitechecks
test-ui.shthe installer's menus, prompts, password handling and input rules (host names, user names)
test-setup-disk.shsetup-disk's plan: partition sizes, minimum disk size, refusal of mounted disks, dry runs that write nothing
test-destructive.shpartitioning and formatting real image files with the system's own flxpart, mkfs and blkid; PARTUUID reading; UUID pins; banner rewrite
test-banner.shthe banner text and logo
test-xsetup-qemu.sh [--uefi] [iso]boots the ISO in QEMU, checks the live system (overlay root, RAM, fastfetch), runs all 13 steps, boots the installed disk, logs in as user and root, checks root filesystem, PARTUUID, /tmp, /home, services, device modes, banner, then reboots and checks that files written in /home and /etc are still there
test-upgrade-qemu.sh old.iso [new.iso]installs an older release, writes files, upgrades with flxupgrade, boots the upgraded disk and checks version, files, passwords, host name, services and that it runs from its root partition
src: test-console.sh, test-flxconsole.sh, test-mdevconf.shthe console service, login on installed systems, the mdev rules

The QEMU tests need an ISO built with SERIAL=1; they drive the guest over its serial port. The suites that need no virtual machine, shellcheck and check-nognu run in GitHub Actions on every push. The QEMU suites are run on BIOS and UEFI before each release, on the exact ISO that is published.

17. Command reference

Commands specific to FreeLinX. Standard commands have manual pages.

xsetup [--list | --status | --reset STEP | -h] [STEP ...]
The installer. See section 5.
xpkg [options] COMMAND [ARGS]
The package manager. Commands: search, show, install, reinstall, remove, autoremove, update, upgrade, outdated, list, info, files, owns, verify, clean, repo list, repo add URL, repo remove URL. Options: -n/--dry-run, -f/--force (override conflict, dependency and downgrade checks), -q, -v, -y (accepted, xpkg never prompts), --root DIR, --allow-unsigned, --no-scripts, -V. See section 11.
flxupgrade [-y] [DISK]
Upgrade an installed system from a newer live ISO. -y does not ask for confirmation. See section 14.
passwd [USER]
Set a password. Without USER, your own (non-root users go through doas). Asks twice without echo.
flxadduser NAME [--admin]
Create a user and set the password. --admin adds the user to wheel.
flxwifi scan | list | connect SSID [PASS] | disconnect | status | off | auto
Wi-Fi. The passphrase can be given in the FLXWIFI_PASS environment variable instead of PASS. auto joins saved networks and is run at boot by the flxwifi service.
flxpart --show DISK
flxpart --create-standard [--esp-size MB] [--flx-sys-size MB] [--dry-run] DISK
flxpart --create-data [--dry-run] DISK
Show a GPT, or write the layout used by the installer: ESP, BIOS boot, system, and (with --flx-sys-size) home. --dry-run prints the layout without writing it.
flxconsole
Run by the shell service. Opens shells (live) or logins (installed) on ttyS0 and tty1 to tty3. See section 8.
flxautomount
Called by mdevd for disks, partitions and optical drives; mounts removable media.
flxifconfig IFACE up | inet ADDR/PREFIX
flxroute add default GATEWAY
Bring an interface up, give it an address; add a default route.
flxnet
Text menu for network setup.
flxtz
Set the time zone.
flxpasswd [-m MIN] [-r] -e
Read user:password pairs on standard input and set the hashes in /etc/shadow. Used by passwd and the installer.
reboot, poweroff
Stop services, unmount filesystems and restart or power off. See section 7.
startx [CLIENT] [-- SERVER OPTIONS]
Start X (package xinit). See section 12.

18. Troubleshooting

"No bootable device" when booting the disk in QEMU
The disk has no system on it. Boot the ISO, run xsetup and finish the setup-disk step. Do not run qemu-img create again on an installed disk; it empties it.
The computer does not boot the USB stick
Disable Secure Boot in the firmware settings. Try the other boot mode (UEFI or legacy BIOS).
The installer seems to hang at "writing the system image"
That message is from releases before 1.2.0, which compressed the whole system during installation. It finishes eventually; under QEMU without KVM it can take a long time. Current releases copy the files instead.
"No space left on device" when installing a package in the live system
The live system keeps changes in RAM. Run xpkg clean to free the downloaded archives, give the machine more RAM, or install to disk first.
startx shows a black screen and no terminal
If you wrote st & and exec openbox in ~/.xinitrc, replace them with exec openbox --startup st, or remove ~/.xinitrc to use the default session. Right click opens the menu.
The Openbox menu shows empty items
No fonts are installed. xpkg install fonts (current openbox packages depend on it).
Xorg stops with keymap errors
Install the XKB data: xpkg install xkeyboard-config (current xorg packages depend on it).
The screen resolution is wrong
In QEMU use -device VGA,xres=1600,yres=900. In X use xrandr.
xpkg: "signature does not verify" or "no keys"
The repository index is not signed by a key in /etc/xpkg/keys, or the key directory is empty. Check /etc/xpkg/repos.conf. Do not use --allow-unsigned with untrusted repositories.
Downloads fail with certificate errors
Check the clock (date). Enable ntpd with xsetup setup-ntp.
Forgotten root password
Choose "Rescue shell" in the boot menu, then run passwd root.
The boot stops with "the root filesystem has errors"
Run e2fsck /dev/... on the device named in the message, then reboot.

19. Repositories

FreeLinX-basebuild scripts, xsetup, flxlive, tests, base releases
srcroot filesystem: /init, system scripts, NetBSD tools, configuration, manual pages
portsconsole ports and their built packages
FreeLinX-deskdesktop edition; build of the shared package stack (Xorg, GTK, Mesa, Firefox, ...)
xpkgpackage manager, repository index and signing tools
toolchainclang, LLD, compiler-rt and the musl sysroot
driversLimine binaries and installer helpers
kernelkernel configuration and build
FreeLinXdesktop releases and this documentation
FreeLinX/packagesthe signed package repository
FreeLinX/sourcesmirror of upstream source archives with checksums

20. Supporting FreeLinX

FreeLinX is developed by volunteers. Donations pay for build machines, test hardware and hosting.

Buy Me a Coffeebuymeacoffee.com/freelinxfoundation
Bitcoinbc1qkp9vt9mv54gv8ze694uhrmlhxnkv9u7jgtu3w3
Ethereum0x7c4c547aadc3d67d74a6fb602664fa2bc473ed58

Bug reports and patches are just as welcome: github.com/FreeLinX/FreeLinX/issues.

FreeLinX base 1.3.1 documentation, October 2026 · BSD-2-Clause · page source