I want to show a project I have been working on and ask for honest feedback.
First, so nobody feels misled: Zainium OS uses the Linux kernel. I did not write
a kernel, and I know most projects on this forum are real from-scratch kernels.
I am posting because everything around the kernel is my own design: the
filesystem layout, the boot program, the package manager and the way the system
protects itself. It is also not a respin. It is not based on Debian, Arch, Alpine
or anything else. Every package is built from source from my own recipes. If this
belongs in another board, please move it and I will not be upset.
WHAT IT IS
Zainium OS is an immutable, rolling release Linux system for x86_64. The first
public ISO came out in November 2026. It is about 2 GB and boots into the COSMIC
1.11.0 desktop. It has 173 base packages and uses Linux 7.3.1, systemd 261.2 and
musl 1.2.6, with glibc 2.44 as an optional package. The boot loader is Limine.
WHY I STARTED
Two things bothered me about normal Linux systems.
The first is that a system can break itself. One bad update, one wrong command,
or one program that overwrites a file, and the machine does not boot.
The second is that nothing in the filesystem has a clear owner. /usr/lib holds
files from the system, from packages and sometimes from the user. When two
programs want different versions of something, they fight. You can not cleanly
separate "the OS" from "what I installed".
So I asked a simple question. What if every file had exactly one owner, and the
system could not be changed while it was running, not even by root?
THE LAYOUT, AND WHY DHS AND NOT FHS
The biggest change you will notice is at the root. There is no /usr, /etc, /var,
/bin, /sbin, /lib, /lib64 or /boot there, and I did not keep them as symlinks. If
you run ls / you get home, root, tmp, run, dev, proc, sys and one more directory
called overlayer. The whole OS lives inside that one.
Inside /overlayer there are three main directories. syshub is the base system,
zexlib is the software you install, and zaisys has the kernel, the initramfs and
the boot loader.
The root itself is a filesystem in RAM, built at every boot. The disk is not the
root. The disk only provides the layers.
I wrote this layout down as an open specification called the Dualayer Hierarchy
Standard (DHS 1.0), under CC BY-SA 4.0. "Dualayer" means two things: dual, for two
C libraries, and layer, for a system built from layers.
Why not just keep FHS? I respect FHS. It has worked for about 30 years. But it
was made for a different time, and these are the places where it did not fit what
I wanted:
- 1. FHS has no idea of layers. It says where a file type goes (binaries here,
libraries there). It does not say who owns the file. In DHS each layer has
one owner: the system layer changes only by a base upgrade, the userland
layer only by the package manager, and the boot layer only by kernel upgrades.
2. FHS assumes one C library. musl and glibc both want /lib, /lib64,
/usr/include, the loader path, ldd and the compiler. On a classic layout they
fight. In DHS each C library has its own directory, named after its target
triplet, so both can live on one system.
3. FHS has no clean split between "the OS" and "the rest". In DHS I can delete
the whole userland layer and the base still boots.
4. Many immutable systems keep FHS and then add tricks on top: bind mounts,
symlinks like /bin to /usr/bin, or moving /home to /var/home. I wanted to see
what happens if the layout is designed for layers from the start
is sys_resolver, a small library preloaded into programs. A path that exists is
never touched. If a lookup fails with ENOENT, it is tried again under the real
roots, so /usr/bin/env opens /overlayer/syshub/bin/env. It also handles scripts
whose #! line points to a missing path, and prebuilt binaries that ask for
/lib64/ld-linux-x86-64.so.2. Packages are also adapted when installed, by changing
the loader, RPATH and script interpreters. It does not cover static or setuid
programs, because the loader preloads nothing into them. That is the weakest part
of the design and I know it.
HOW MY IMMUTABILITY IS DIFFERENT
Please correct me if I describe another project wrongly. This is my understanding.
Fedora Silverblue: the system is an OSTree deployment. An update makes a new
deployment and you reboot into it. It keeps an FHS style tree.
openSUSE MicroOS: uses a read-only Btrfs root with snapshots. An update goes
into a new snapshot and applies after a reboot.
NixOS: the store is read-only, and each system is a generation you can switch
to. It is a very different idea, with paths based on hashes.
Ubuntu Core: the system and apps are snap packages.
These are all good projects, and much more mature than mine. The differences in
Zainium OS are:
- Installing a program needs no reboot, and the program appears at once, while
the base stays read-only. Base upgrades still apply at the next boot.
- The read-only mount can not be undone from the running system. On a normal
read-only mount, root can run "mount -o remount,rw". On a sealed overlay this
fails with EROFS for root too.
- Every base file is sealed with fs-verity, so the kernel refuses to change the
content of the file.
- Upgrades are file by file with a journal, in the initramfs, before anything
uses the base. It is not a whole new image or snapshot. This is simple, but it
also means I do not get the "two full copies" safety that A/B systems have.
There are limits, and I want to be clear about them. Root can not touch the base,
but root can still write to the userland layer, and a file there can shadow a base
file in the merged view. So a compromised root can not change the base on disk,
but it can change what programs see. The /etc and /var trees are also writable
on purpose. Immutable here means "the base stays clean and recoverable", not "root
is powerless". You can always boot the base alone with zainium.overlay=off.
THE KERNEL PATCH: SEALED OVERLAYFS
This is the part I am most interested to get feedback on.
The view at /overlayer/syshub is an overlay with the base as the lower layer and
installed software as the upper layer. Stock OverlayFS gave me two problems:
1. A read-only overlay can be remounted writable by root.
2. The upper layer must not change while the overlay is mounted. The docs call
this undefined behaviour, because the kernel caches lookups.
I added a "sealed" mount option, about 80 lines in fs/overlayfs. A sealed overlay
is forced read-only through the same path that keeps an overlay with no upper
layer read-only, so a remount fails. The package manager writes straight to the
upper directory, and the overlay has to notice.
To do that, negative dentries and directories with no upper alias are checked
against the upper parent on lookup, and merged directory listings are not cached.
I found two bugs the hard way, and they taught me a lot:
- ESTALE on a live system. On the live ISO the upper layer starts empty. The first
package install created upper/bin while programs from bin were running. The old
directory inode was still in the cache, pinned by those programs. A new lookup
found the same lower inode, but now with an upper too, and ovl_verify_inode()
returned ESTALE. Suddenly ls and bash were "not found", and drop_caches did not
help. The fix: on a sealed overlay, an inode with an upper is hashed by the upper
inode, not the lower one.
- Old files after an upgrade. A package upgrade renames a new file over the old
one in the upper layer. The merged dentry still pointed to the old, unhashed
upper dentry, so programs saw the old file, and a removed program still ran. The
fix: a dentry whose upper is unhashed is treated as stale and looked up again.
Is hashing by upper a safe idea? Is this the right place for these checks? I would
really like someone who knows the VFS to tell me what I am missing.
THE BOOT: QUANTRA-RAMFS
The initramfs is one static Rust program, about 7000 lines, with no shell and no
busybox. It runs as /init and does this:
1. Finds the disk by UUID, label, device or loop image, and assembles RAID and LVM
if asked.
2. Unlocks LUKS with a TPM2, a key file or a passphrase.
3. Loads the display driver and starts the splash.
4. Applies a staged base upgrade, with a journal. A failed or interrupted upgrade
is undone, or the boot stops. It never continues on a half applied base.
5. Makes sure every base and boot file is sealed with fs-verity.
6. Builds the view: the sealed overlay, writable etc and var, and a read-only
zaisys.
7. Detaches the raw disk, does pivot_root and starts systemd.
If it can not build the protected view, it stops in a small emergency shell with
built-in ls, cat, mount, reboot and poweroff, and does not start an unprotected
system.
PACKAGES: ZEX
zex is my package manager, one static Rust binary with its own signed .zex format.
Base packages go to the system layer and everything else goes to the userland
layer. It can also install from Void, Alpine and Chimera repositories by adapting
their packages. Base upgrades are staged for the next boot, and userland changes
are transactions you can roll back.
TWO C LIBRARIES
The base is built with my own x86_64-zainium-linux-musl toolchain. musl needed
patches, because its paths like /etc/passwd and the default library path do not
exist here. glibc installs in its own directory with its own compiler. A program
can be built for either one, and each loads only its own libraries. I ran 35
checks on this and published the report.
OTHER PARTS
- elevate: my sudo replacement, in Rust.
- elevate-pam: a Linux-PAM implementation in Rust with the same C ABI and TOML
service files.
- userutiles: 175 command line tools in Rust, covering coreutils and part of
util-linux, one binary per tool.
- Security: kernel lockdown in integrity mode, signed modules, AppArmor, Landlock,
IPE, Yama and BPF LSM.
CURRENT STATE
I use it daily on my own laptop. It has had very little testing on other hardware.
It needs an AVX2 CPU and 4 GB of RAM. BIOS boot works from USB, but not from a disc.
The installer has no disk encryption option yet. I expect bugs.
QUESTIONS FOR YOU
1. Is the sealed patch sound? Are there races or cases I missed, like hard links,
renames of directories, or file handles?
2. Would something like this be useful upstream, or is it too specific?
3. Is dropping FHS worth it, or does the pain of LD_PRELOAD path resolution make
it a bad trade? Is there a better way than a preload library?
4. How should the shadowing problem be solved, where root writes to the upper
layer and hides a base file?
5. Is a file by file journaled upgrade in the initramfs good enough, or should I
move to A/B images?
6. Does the initramfs do too much? Should some of this live in the real system?
7. What would you change in the DHS spec?
LINKS
Download: https://zainiumdynamics.tech/download
Kernel notes: https://zainiumdynamics.tech/docs/kernel
Sealed patch: https://github.com/Zainium-Dynamics/ker ... ount.patch
Boot notes: https://zainiumdynamics.tech/docs/quantra-ramfs
DHS specification: https://zainiumdynamics.tech/dhs
Security report: https://zainiumdynamics.tech/news/secur ... 2026-11-02
Source: https://github.com/Zainium-Dynamics
The live ISO starts without a login. If the screen locks, the user is zainium and
the password is where_is_usr_bin.
Thanks for reading. Please be as critical as you like.
Ali Zain