Partitioning notes

UraniumOS requires UEFI. BIOS boot does not work; see differences and known failures. The steps of the manual install are described in manual install.

The layout the installer creates

PartitionSizeTypeFilesystemLabelMount point
1512 MiBEFI SystemFAT32URANIUMESP/boot
2the rest of the diskLinux filesystembtrfsuranium/ and /home

The partition table is GPT. The installer gives the following to sfdisk:

label: gpt
,512M,U
,,L

U is the EFI System partition type, and L is the Linux filesystem type. Before it partitions, the installer unmounts everything that is mounted from the disk and runs wipefs -af on it, which removes every old signature on the whole disk. Swap is a zram device; see storage.

The name of a partition is the name of the disk plus the number, with a p in between when the name of the disk ends in a digit. For example, /dev/vda gives /dev/vda1 and /dev/vda2, and /dev/nvme0n1 gives /dev/nvme0n1p1 and /dev/nvme0n1p2. SD and eMMC disks such as /dev/mmcblk0 are named in the same way, but the kernel has no MMC driver; see hardware status.

Filesystems and subvolumes

The installer runs mkfs.vfat -F32 -n URANIUMESP on the first partition and mkfs.btrfs -q -f -L uranium on the second. It then mounts the btrfs filesystem and creates two subvolumes at the top level:

@
The root filesystem. It holds /nix, /rod, /etc, /var and everything else except the home directories and the boot partition.
@home
Mounted on /home.

The top level of the filesystem (subvolume id 5) is mounted at /run/scram/top. It holds @, @home and .scram, the directory in which scram keeps it's frames. The mount is not in the fstab: the initramfs mounts the top level and moves it into the new root, and /etc/rc keeps it accross the mount of the tmpfs on /run. If it is missing, scram mounts the top level itself when a command needs it.

After it unpacks the system, the installer runs scram apply. That command turns the directories that are excluded in /etc/scram.d/00-uranium.conf into nested subvolumes of @:

# index and caches
-/rod/nixpkgs
-/var/cache
-/var/tmp

A btrfs snapshot leaves nested subvolumes out, so the package index in /rod/nixpkgs and the two cache directories are not in any frame and a flash does not change them. The names of these subvolumes are recorded in .scram/keep.list on the top level. scram doctor reports an excluded path that is not a subvolume.

The mounts

The installer writes the following fstab, and the manual install writes the same three lines.

LABEL=uranium      /      btrfs  noatime,subvol=@                       0 0
LABEL=uranium      /home  btrfs  noatime,nodev,nosuid,subvol=@home       0 0
LABEL=URANIUMESP   /boot  vfat   umask=0077,nodev,nosuid,noexec         0 2

The filesystems are found by label, so the same fstab works on virtio, SATA and NVMe disks. The boot menu entries use root=LABEL=uranium. For a different label, change the fstab and set SCRAM_LABEL when scram boot runs, see boot. /tmp and /dev/shm are tmpfs mounts made by /etc/rc with nosuid and nodev, and /dev/shm also has noexec.

/etc/rc calls mount -a but not swapon -a, so a swap partition in the fstab stays off until /etc/rc.local runs swapon for it. The kernel has no hibernation support, so a swap partition cannot hold a resume image.

Sizes

Boot partition
512 MiB. In the 0.1.0 image one kernel and initramfs are 20,050,944 bytes and 7,937,015 bytes, together about 27 MB, and BOOTX64.EFI from limine is 352,256 bytes. scram boot copies the kernel and initramfs of the profile to /boot/uranium. It also keeps the kernel of every frame that was taken with a different kernel, up to three more sets (SCRAM_KERNELS), so the boot partition holds at most four sets, about 110 MB. geiger warns when the boot partition is 80% full and fails at 92%, because a full boot partition breaks scram boot and with it the frame entries.
Root
The root of the 0.1.0 image is 738 MB in apparent size. The package index adds a further 197 MB when rod +s fetches it (the tarball is 40 MB). Packages are added to this. Frames are btrfs snapshots that share blocks with the live system, so a frame costs only the blocks that change after it is taken, and a store that changes a lot costs more. scram doctor warns below 10% free space, and geiger warns at 85% and fails at 95%.
Disk size
The installer does not check the size of the disk that it is given.

Partitioning by hand

The following commands make the layout of the installer. You must replace /dev/sdX with the disk. Everything on the disk is erased.

# test -d /sys/firmware/efi
# wipefs -af /dev/sdX
# printf 'label: gpt\n,512M,U\n,,L\n' | sfdisk /dev/sdX
# mkfs.vfat -F32 -n URANIUMESP /dev/sdX1
# mkfs.btrfs -q -L uranium /dev/sdX2
# mount /dev/sdX2 /mnt
# btrfs subvolume create /mnt/@
# btrfs subvolume create /mnt/@home
# umount /mnt
# mount -o noatime,subvol=@ /dev/sdX2 /mnt
# mkdir -p /mnt/home /mnt/boot /mnt/run/scram/top
# mount -o noatime,subvol=@home /dev/sdX2 /mnt/home
# mount /dev/sdX1 /mnt/boot
# mount -o subvolid=5 /dev/sdX2 /mnt/run/scram/top

After sfdisk, wait until /dev/sdX1 and /dev/sdX2 appear, or run blockdev --rereadpt /dev/sdX. The first command fails when the live system was not started through UEFI. The last mount lets scram find the top level in the chroot. The rest follows in manual install.

scram frames the subvolumes @ and @home of one btrfs filesystem (SCRAM_SUBS defaults to @ @home), and the initramfs mounts @ from the filesystem it finds by label. The layout therefore needs btrfs with @ and @home.

Encryption in the manual install

The installer cannot encrypt. The manual install can encrypt the second partition with LUKS. The initramfs contains cryptsetup, and the kernel has dm-crypt, XTS and AES built in, together with the AES-NI implementation. The steps differ from the plain ones in four places.

1. The root is a LUKS container

Format and open the second partition, and put btrfs on the mapped device. The script passes the passphrase on standard input; leave out --key-file=- and the pipe so that cryptsetup asks.

# cryptsetup luksFormat --batch-mode --key-file=- /dev/sdX2
# cryptsetup open --key-file=- /dev/sdX2 uranium-root
# mkfs.btrfs -q -L uranium /dev/mapper/uranium-root

Create the subvolumes and the mounts as above, with /dev/mapper/uranium-root in place of /dev/sdX2. The partition /dev/sdX1 stays as it is. The cipher and key size are the defaults of the cryptsetup in the package index. Check them with cryptsetup luksDump /dev/sdX2.

2. The fstab is unchanged

The fstab still names LABEL=uranium. The label is on the btrfs filesystem inside the container, and it is visible once the container is open.

3. The kernel is told which device to unlock

The initramfs reads uranium.luks= from the kernel arguments. The value is a device path or anything findfs understands. Use the UUID of the LUKS partition and put it in /etc/cmdline:

# echo uranium.luks=UUID=$(blkid -s UUID -o value /dev/sdX2) > /mnt/etc/cmdline

This replaces the whole file, which holds slab_nomerge vsyscall=none debugfs=off randomize_kstack_offset=on on a fresh system. To keep these four hardening arguments, write everything on one line: scram boot pastes the file into a single cmdline: entry, so a second line breaks the menu.

The initramfs waits up to 20 seconds for the device, then runs cryptsetup open and asks for the passphrase on the console, three times at most. With uranium.luks.key=PATH the initramfs calls cryptsetup open --key-file PATH and does not ask. The initramfs contains no key file and mounts nothing before this step, so the path has to be a device node.

4. The boot files are copied by hand

Copy the kernel, the initramfs and limine to the boot partition and write limine.conf yourself instead of running scram boot:

# mkdir -p /boot/uranium /boot/EFI/BOOT
# cp -L /rod/profile/boot/vmlinuz /rod/profile/boot/initrd /boot/uranium/
# cp -L /rod/profile/share/limine/BOOTX64.EFI /boot/EFI/BOOT/
# cat > /boot/limine.conf
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 uranium.luks=UUID=<uuid>

Run these in the chroot. After the first rod +i, rod +r or rod +u, rod runs scram boot because limine.conf exists, and that command writes the menu again with the frame entries and the arguments from /etc/cmdline.

What the encryption covers

The container holds the root filesystem: @, @home, every frame and the package store. The boot partition is FAT32 and is not encrypted. It holds the kernel, the initramfs, limine and the menu. Anyone with access to the disk can replace those files, and nothing checks them at boot because Secure Boot is not supported. The encryption protects the disk while the machine is off. It does not protect against a modified boot partition, and the key is in kernel memory while the container is open.

The passphrase is the only way to the data. LUKS keeps a copy of the header on the partition only; back it up with cryptsetup luksHeaderBackup to a medium that is not on this disk. Frames are on the same disk and do not help when the header is damaged, see backups.

Checking the result

On the installed system:

# findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS /boot /home /
# btrfs subvolume list /
# scram doctor

scram doctor checks that @ and @home exist, that the excluded paths are subvolumes, that a boot menu is present and that the kernel on the boot partition is the one in the profile.