Boot

UraniumOS boots on UEFI machines. The firmware starts limine from the EFI System Partition, limine loads the kernel and an initramfs, and the initramfs finds the root, applies pending changes from scram, mounts the root and starts busybox init, which runs /etc/rc and runsvdir (see services). The kernel has no module support. BIOS and Secure Boot are not supported.

The boot partition

The partition has the label URANIUMESP, is FAT32 and is mounted on /boot with umask=0077,nodev,nosuid,noexec. scram boot writes everything on it except what the firmware needs to find limine:

/boot/EFI/BOOT/BOOTX64.EFI      limine, copied from the profile
/boot/limine.conf               the menu
/boot/uranium/vmlinuz           the kernel of the profile
/boot/uranium/initrd            the initramfs of the profile
/boot/uranium/k-HASH/vmlinuz    a kernel kept for older frames
/boot/uranium/k-HASH/initrd

The firmware has to start EFI/BOOT/BOOTX64.EFI, the path UEFI uses for removable media. Nothing in UraniumOS writes a boot entry into the firmware, so if the firmware does not start from this path, add an entry in its setup.

The partition is not part of any frame. To rebuild it, run scram boot from the installed system, or from a live medium with the root mounted. It copies the kernel, the initramfs and limine again and writes the menu. See manual install for the chroot.

The boot menu

scram boot [N] writes limine.conf and the files above. The menu is also rewritten by every scram frame when /boot/limine.conf exists, and after every successful rod +i, +r, +u and +a. Those automatic runs list all frames and hide errors, so run scram boot to see one. The initramfs never writes to the partition, so frames it takes (pre-commit, pre-flash, pre-revert) show in the menu only after the next run.

timeout: 3
serial: yes

/UraniumOS
    protocol: linux
    path: boot():/uranium/vmlinuz
    module_path: boot():/uranium/initrd
    cmdline: root=LABEL=uranium rw console=tty0 console=ttyS0,115200 slab_nomerge vsyscall=none debugfs=off randomize_kstack_offset=on

/Frame 20261029-030000-auto
    protocol: linux
    path: boot():/uranium/vmlinuz
    module_path: boot():/uranium/initrd
    cmdline: root=LABEL=uranium rw console=tty0 console=ttyS0,115200 slab_nomerge vsyscall=none debugfs=off randomize_kstack_offset=on uranium.frame=20261029-030000-auto

/Frame 20261029-010000-auto
    protocol: linux
    path: boot():/uranium/k-aaaaaaaaaaaa/vmlinuz
    module_path: boot():/uranium/k-aaaaaaaaaaaa/initrd
    cmdline: root=LABEL=uranium rw console=tty0 console=ttyS0,115200 slab_nomerge vsyscall=none debugfs=off randomize_kstack_offset=on uranium.frame=20261029-010000-auto

/Rescue shell
    protocol: linux
    path: boot():/uranium/vmlinuz
    module_path: boot():/uranium/initrd
    cmdline: root=LABEL=uranium rw console=tty0 console=ttyS0,115200 slab_nomerge vsyscall=none debugfs=off randomize_kstack_offset=on uranium.shell

This is scram boot output, shortened. The second frame was taken with another kernel.

timeout: 3, serial: yes
limine waits three seconds and then starts the first entry. The menu also shows on the serial port, so a VM or a headless machine can pick an entry there. The kernel console is console=tty0 console=ttyS0,115200.
UraniumOS
The system as it is. It is first, so it is the default.
Frame ID
One entry per frame, newest first. The entry adds uranium.frame=ID to the command line, and the initramfs boots writable copies of the frame, see below. scram boot 20 lists only the newest twenty, but the next automatic run lists all frames again. Only automatic frames are pruned, so the menu grows.
Rescue shell
Adds uranium.shell. The initramfs opens a shell before it touches the root.

The command line of each entry is root=LABEL=uranium rw console=tty0 console=ttyS0,115200, then the contents of /etc/cmdline (keep it to one line), then the argument of the entry. The label comes from SCRAM_LABEL. The stage ships /etc/cmdline with slab_nomerge vsyscall=none debugfs=off randomize_kstack_offset=on, see kernel. Run scram boot after editing the file. Press e in the menu to change one boot without saving it.

Kernels in frames

A frame stores the kernel and initramfs of the profile when it was taken, and an id that is a hash of both. scram boot compares that hash with the current kernel. A frame with a different kernel boots from /boot/uranium/k-HASH/. Each kernel is copied there once, and at most three are kept (SCRAM_KERNELS). A frame that would need a fourth boots with the current kernel, and so does a frame without a stored kernel, such as frames taken by the initramfs or received with scram receive. A k-HASH directory that no frame uses is deleted.

After a flash, a commit or a revert the partition still holds the earlier kernel. scram doctor warns about it and scram boot fixes it. A new kernel from rod +u reaches the partition at the next scram boot, which is the next frame or rod operation, or in an hour at the latest through autoframe. geiger warns when the running kernel differs from the one in the profile.

Kernel arguments

The initramfs reads these from /proc/cmdline. Other arguments go to the kernel.

root=SPECThe btrfs filesystem. SPEC is LABEL=, UUID=, PARTUUID= or PARTLABEL= with a value, or the path of a block device. The initramfs waits up to 20 seconds for it.
init=PATHThe program to start after switch_root. Default /rod/init/bin/init.
uranium.frame=IDBoot writable copies of the frame ID. Use the full directory name.
uranium.shellOpen a shell in the initramfs before anything else. exit continues the boot.
uranium.luks=DEVICEUnlock an encrypted device first, see storage.
uranium.luks.key=FILEUnlock with this key file and ask no passphrase.
uranium.liveStart from the ISO. The live medium sets it.

The initramfs

The initramfs is the package initramfs-uranium: a gzip-compressed cpio archive with uranium-init.sh as /init, btrfs and cryptsetup, and some busybox applets. It has no scram, rsync or cp. The steps, in order:

  1. Mount /proc, /sys and /dev and read the command line. With uranium.shell, say that a shell is starting and run it.
  2. With uranium.live, find the medium (below) and stop here.
  3. With uranium.luks, unlock the device as uranium-root.
  4. Wait up to 20 seconds for the root device and mount its top level (subvolume 5) on /top.
  5. Unless a frame boot was asked for, revert an update that was not confirmed.
  6. Carry out the action in .scram/boot-action.
  7. With uranium.frame, make writable copies of the frame.
  8. Mount the root subvolume (and for a frame boot the home copy on /home), move the top level to /run/scram/top in the new root, unmount /proc, /sys and /dev and run switch_root.

When a step fails, the initramfs reports what failed, says that it is dropping to a shell, and runs the shell. These are the causes:

The initramfs stops and drops to a shell when the root device does not appear within 20 seconds, when a device or the root subvolume cannot be mounted, when the frame named in uranium.frame= does not exist, when the encrypted device is missing or cannot be unlocked, or when the live medium or its image cannot be found or mounted.

The shell is busybox sh. To look at the filesystem from it, run mount -t btrfs -o subvolid=5 /dev/DEVICE /top. Leaving the shell with exit after a failure ends init and the kernel panics.

Live medium

The ISO has its own limine menu with the entries "UraniumOS live" and "UraniumOS live, shell in the initramfs", both with uranium.live. The initramfs looks for an ISO 9660 filesystem with /live/root.squashfs on every /dev/sr*, /dev/vd*, /dev/sd* and /dev/nvme*n* for up to 30 seconds. It mounts the image through a loop device, puts a tmpfs of 75% of the memory on top with an overlay, moves the mounts to /run/live and starts init. Changes in the live system stay in memory.

Booting a frame

The initramfs snapshots .scram/ID/@ and @home into .scram/live/@ and @home, replacing the copies of an earlier frame boot, and mounts them as root and as /home. It tells you that it is booting the frame and that changes are dropped at the next frame boot. The frame itself is not touched, so nothing on disk changes except .scram/live, which uses some space until the next frame boot replaces it. Everything done in a frame boot, including packages, files and home directories, is lost at the next frame boot. To keep the state of a frame, boot it and run scram flash FRAME or scram flash --boot FRAME from there.

A frame boot does not revert an update on trial and does not count as a boot for it.

Changes at boot

The root subvolume cannot be replaced while it is mounted, so scram commit --boot and scram flash --boot write one line to .scram/boot-action and the initramfs carries it out. scram status shows it while it is waiting, and scram cancel deletes it. The initramfs deletes the file after it acts, whatever the result.

commitIf .scram/try/@ exists: take a frame labelled pre-commit, replace @ with the copy, delete .scram/try, write the id of pre-commit to .scram/trial and create .scram/trial-boot. It tells you that the update was committed on trial and which frame undoes it. With no copy, it does nothing.
flash ID, flash ID @If the frame exists: take a frame labelled pre-flash, replace @ and @home (or only @) with copies of the frame, and tell you which frame it flashed and how to undo it. A frame that does not exist is skipped without a message.

Replacing a subvolume moves it's left-out subvolumes into the new copy, deletes the old one and moves the new one into its place. Frames taken by the initramfs have two snapshots and nothing else, no note and no kernel copy.

Trial boots and the revert

A commit at boot leaves the system on trial. Two files in .scram describe it, trial with the id of the pre-commit frame and an empty trial-boot. The initramfs applies these rules in order:

  1. At a normal boot, when trial exists: if trial-boot also exists and the pre-commit frame still has its @, take a pre-revert frame, replace @ with a copy of pre-commit and tell you that the update was not confirmed and was reverted. Then delete both files. Home directories are not changed.
  2. The commit action, if scheduled, runs after that and starts a new trial.
  3. Booting a frame skips the first rule and leaves the files for the next normal boot.

An update survives one boot unless scram confirm removes trial-boot before the next normal boot. The trial service does that by running scram verify at every boot. It waits five minutes, runs geiger up to three times and confirms on a pass. If geiger still fails, verify reboots and the initramfs reverts. If the system cannot start at all, the next reset boots into the revert. scram lists verify and its variables.

# rod +u
updating in a copy of the system
...
update ok, pending: commit with scram commit --boot
# scram commit --boot
commit scheduled for the next boot, it reverts unless you run scram confirm or the system stays healthy
# reboot
...
update committed on trial, undo with frame 20261102-165517-pre-commit

The same update, not confirmed:

# reboot
...
update was not confirmed, reverted to frame 20261102-165517-pre-commit, undo with frame 20261102-165524-pre-revert

The ids are examples. To undo a revert, flash the pre-revert frame. To undo a confirmed commit, flash the pre-commit frame.

The revert replaces @ and not @home, it applies only at the first normal boot after a commit, and it needs a normal boot to be possible. If limine, the kernel or the partition is broken, choose a frame in the menu or use the live medium.

After the initramfs

The init program is busybox init with this /etc/inittab:

::sysinit:/etc/rc
::respawn:/rod/profile/bin/runsvdir -P /etc/service
::ctrlaltdel:/rod/init/bin/reboot
::shutdown:/etc/rc.shutdown

/etc/rc mounts /proc, /sys, /dev, /dev/pts, /dev/shm, /tmp and /run, keeps /run/scram/top across the new tmpfs, remounts the root read-write and runs mount -a. Then it sets the hostname, loads the keymap, /etc/sysctl.conf and /etc/nftables.conf if they exist, sets up zram swap, brings up lo and runs /etc/rc.local. runsvdir starts the services in /etc/service. /etc/rc.shutdown runs sync and umount -a -r.

When it does not boot

  1. Choose a Frame entry from before the change. It changes nothing on disk. If it works, run scram flash FRAME to keep it, see scram.
  2. If no menu appears the firmware did not start limine from the boot partition. If the partition is empty or damaged, boot the live medium, mount the root and the partition as in the manual install, and run scram boot in the chroot.
  3. If the initramfs stops in a shell, read the message above it. If it says that no root device was found, the label is not uranium, or the disk is not visible.
  4. The Rescue shell entry gives a shell in the initramfs.

More is in troubleshooting.