॥ श्री ॥

Boot Process

Architecture 2026-08-28

Shanios uses a fully measured, signed boot chain built on systemd-boot, dracut-generated Unified Kernel Images (UKIs), and optionally TPM2 + Secure Boot verification.

Boot Chain Overview

UEFI Firmware
  └─ systemd-boot (EFI binary at /boot/efi/EFI/BOOT/BOOTX64.EFI via shim)
       └─ UKI: shanios-blue.efi  or  shanios-green.efi
            ├─ Linux kernel
            ├─ initramfs (dracut)
            ├─ kernel command line (embedded — tamper-evident)
            └─ OS stub (systemd-stub)
                  └─ dracut initrd
                       ├─ LUKS2 unlock (passphrase or TPM2 auto-unlock)
                       ├─ @data mount (for boot failure + /etc overlay)
                       ├─ boot_hard_failure marker written (pre-mount hook)
                       ├─ Btrfs root mount (@blue or @green, read-only)
                       ├─ OverlayFS /etc mount (pre-pivot hook)
                       ├─ boot_hard_failure cleared on success (pre-pivot hook)
                       └─ Switch root → systemd PID 1

Unified Kernel Images (UKIs)

Each slot has its own UKI — a single EFI PE binary that bundles the kernel, initramfs, and kernel cmdline together:

FileSlot
/boot/efi/EFI/shanios/shanios-blue.efi@blue
/boot/efi/EFI/shanios/shanios-green.efi@green

UKIs are generated by gen-efi configure <slot> — it builds the image with dracut --force --uefi --kver <kver> --kernel-cmdline <cmdline> <path>.tmp, signs it with the MOK key via sbsign, verifies the signature with sbverify, then renames it into place. Because the cmdline is embedded and signed, it cannot be tampered with from the boot menu.

gen-efi also keeps the rest of the ESP current: on every configure run it copies shimx64.efi, systemd-bootx64.efi, and mmx64.efi from /usr/share/shim-signed/ and /usr/lib/systemd/boot/efi/ onto the ESP whenever the source is newer than what's installed, and re-signs grubx64.efi (systemd-boot) with the MOK key. This is necessary because bootctl update would otherwise deploy an unsigned binary on a Secure Boot system — gen-efi is the only path that keeps signatures valid across kernel/systemd updates.

The ESP also holds:

  • EFI/BOOT/BOOTX64.EFI — shim (Microsoft-signed first-stage loader)
  • EFI/BOOT/grubx64.efi — systemd-boot (MOK-signed second-stage, loaded by shim)
  • EFI/BOOT/mmx64.efi — MokManager (for key enrollment at UEFI prompt)
  • EFI/BOOT/MOK.der — local MOK public certificate (for MokManager fallback)

Boot Entries

systemd-boot displays two entries:

shanios-blue  (Active)      ← currently running slot
shanios-green (Candidate)   ← standby / previous slot

The (Active) entry is written as shanios-<slot>+3-0.conf (3 tries left, 0 done) and set as the loader.conf default; systemd-boot matches the default against the entry ID (shanios-<slot>.conf, tries suffix stripped), so the tries-suffixed filename still resolves correctly no matter how many boots have elapsed. The (Candidate) entry is written as a plain shanios-<slot>.conf with no tries suffix — it is the unconditional fallback if the active slot's tries run out.

UEFI Firmware Boot Entries (efibootmgr)

The entries above are systemd-boot's own internal menu. Separately, the UEFI firmware itself keeps a boot menu (visible via your firmware setup or a one-time boot-device key like F12) that decides which bootloader to launch at all — this is managed by efibootmgr (pre-installed), and is what the installer registers automatically during setup.

# List current UEFI firmware boot entries and boot order
sudo efibootmgr -v

# Re-register Shanios's boot entry (e.g. after it was removed by another OS's installer)
sudo efibootmgr --create --disk /dev/sda --part 1 --label "Shanios" --loader '\EFI\systemd\systemd-bootx64.efi'

# Change boot order (comma-separated boot entry numbers, first = highest priority)
sudo efibootmgr --bootorder 0000,0001,0002

# Remove a stale/duplicate entry
sudo efibootmgr --bootnum 0003 --delete-bootnum

Kernel Command Line

Key parameters embedded in each UKI:

quiet splash                         # suppress kernel messages; show Plymouth
systemd.volatile=state               # /var is tmpfs; bind-mounts restore service state
ro                                   # read-only root
lsm=landlock,lockdown,yama,integrity,apparmor,bpf
rootfstype=btrfs
rootflags=subvol=@blue,ro,noatime,compress=zstd,space_cache=v2,autodefrag
rd.luks.uuid=<uuid>                  # LUKS container UUID (encrypted systems only)
rd.luks.name=<uuid>=shani_root       # maps the LUKS UUID to /dev/mapper/shani_root
rd.luks.options=<uuid>=tpm2-device=auto   # TPM2 auto-unlock hint
root=/dev/mapper/shani_root          # or root=UUID=<fs-uuid> when encryption is disabled
rd.vconsole.keymap=us                # keyboard layout for LUKS passphrase prompt (from /etc/vconsole.conf, if set)
resume=UUID=<uuid>                   # hibernation resume device (when /swap/swapfile exists)
resume_offset=<offset>               # from `btrfs inspect-internal map-swapfile -r /swap/swapfile` (when swap configured)

The cmdline is generated by gen-efi configure <slot>'s generate_cmdline() and written to /etc/kernel/install_cmdline_<slot> before being embedded in the UKI via dracut --kernel-cmdline. This file is regenerated on every deploy and cannot be manually pre-edited — it is always overwritten. If UUID/LUKS detection fails transiently (e.g. inside a chroot), gen-efi falls back to keeping the existing cmdline file rather than writing a broken one.

The 99shanios Dracut Module

Shanios ships a custom dracut module at /usr/lib/dracut/modules.d/99shanios/ that adds three hooks to the initramfs:

HookPhasePriorityPurpose
shanios-boot-failure-hook.shpre-mount90Writes /data/boot_hard_failure before root is mounted — persists if root mount fails
shanios-overlay-etc.shpre-pivot50Mounts the /etc OverlayFS before pivot_root so systemd PID 1 sees user config from its first read
shanios-boot-success-clear.shpre-pivot90Clears boot_hard_failure after root mounts successfully

See Dracut Initramfs Module for full hook implementation details, mount options, and interaction with boot health services.

How gen-efi Runs Against the Candidate Slot

shani-deploy never runs gen-efi directly against the live root — the candidate slot's UKI is built inside a chroot of the inactive subvolume so the new kernel/initramfs come from the new slot, not the booted one. prepare_chroot() sets this up with two groups of bind mounts (shani-deploy.sh):

GroupDirsMountPurpose
CHROOT_BIND_DIRS/dev /proc /sys /run /tmpmount --rbindStandard chroot plumbing so dracut, sbsign, mokutil, etc. work normally
CHROOT_STATIC_DIRSdata etc var swapmount --bindLive host state the candidate's gen-efi needs but that shouldn't come from the new slot

CHROOT_STATIC_DIRS is bound from the live, booted system, not the candidate slot, deliberately:

  • data/data/downloads and the slot markers gen-efi may need
  • etc/etc/secureboot/keys (MOK signing keys), /etc/vconsole.conf (keymap), /etc/kernel/install_cmdline_* (regenerated from live disk state)
  • var — dracut's cache and module state
  • swap — the shared @swap subvolume, so gen-efi can read the real swapfile's resume_offset

Consequence: gen-efi inside the chroot always sees the live /etc, not the new slot's /etc. This is intentional for keys and keymap (they're shared state), but it means if the new slot ships a different fstab or vconsole.conf, those changes don't affect the UKI cmdline until the next deploy after already booting into the new slot. /sys/firmware/efi/efivars is also rbind-mounted in (when present) so mokutil can reach the real EFI variable store from inside the chroot. /boot/efi is bind-mounted (or freshly mounted by label if not already mounted on the host) so gen-efi writes UKIs to the real ESP.

Boot Health & Automatic Rollback

Shanios uses systemd-boot's boot-counting mechanism plus its own two-tier failure detection:

Tier 1: Boot Counter (systemd-boot)

  1. +3-0 — set on the new slot's EFI entry at deploy time (3 tries left, 0 done)
  2. On each failed boot, the firmware decrements the tries-left counter
  3. At 0, systemd-boot falls back to the (Candidate) entry — before the OS even starts
  4. Once bless-boot.service runs successfully, the entry file is renamed +3-0+3-3 (tries-left == tries-done), which stops the countdown for good

Tier 2: Application-level Health (systemd services)

ServiceWhenWhat
mark-boot-in-progress.servicelocal-fs.target (requires data.mount)rm -f boot-ok boot_failure.acked boot_in_progress, then touch boot_in_progress — hard-fails (blocking the boot) if /data isn't mounted
mark-boot-success.servicemulti-user.target, requires boot_in_progress to existWrites /data/boot-ok, removes boot_in_progress, and — if the slot that just booted matches the slot recorded in boot_failure — clears boot_failure/boot_failure.acked as stale (the slot clearly recovered)
bless-boot.servicemulti-user.target, ConditionPathExists=/data/boot-okRuns bootctl set-good — stops the boot counter countdown
check-boot-failure.timer / .serviceOnBootSec=15m, fires onceIf boot_in_progress still exists and boot-ok is absent, writes the previously active slot name into /data/boot_failure (skipped if /data/boot_hard_failure is already present)

On first login after a fallback, shani-update detects the mismatch between the booted slot and /data/current-slot, then offers rollback.

Boot Marker Files

FileLocationMeaning
boot_in_progress/data/Boot started but not yet confirmed successful
boot-ok/data/System reached multi-user.target successfully
boot_failure/data/Soft failure: contains the name of the slot that failed to reach multi-user.target and was fallen back from
boot_failure.acked/data/User acknowledged the failure; rollback was offered
boot_hard_failure/data/Hard failure: contains the attempted slot name (or unknown); written by the pre-mount dracut hook before root mount is even attempted, cleared by the pre-pivot hook only on success

LUKS2 Unlock

If full-disk encryption is enabled, the initramfs unlocks the LUKS2 container before mounting the Btrfs partition:

  • Manual: Plymouth passphrase prompt (keyboard layout from rd.vconsole.keymap)
  • TPM2 auto-unlock: systemd-cryptenroll binds the key to PCR values; no passphrase required if the boot chain is unmodified
  • Recovery: Boot from USB → cryptsetup open /dev/sdXY shani_root → mount and repair

The initramfs includes /etc/crypttab (via dracut.conf.d/99-crypt-key.conf) so the LUKS device is known at early-boot time.

Checking Boot State

# Which slot am I running?
cat /data/current-slot

# systemd-boot status and boot entries
bootctl status
bootctl list

# Boot counter for current entry
bootctl status | grep -A3 "Current Boot"

# Boot marker state
ls -la /data/boot-ok /data/boot_in_progress /data/boot_failure /data/boot_hard_failure 2>/dev/null

# Recent boot logs
journalctl -b 0 --no-pager | head -60

# Previous boot (useful after a rollback)
journalctl -b -1 --no-pager | head -60

# Boot chain service status
systemctl status mark-boot-success.service bless-boot.service check-boot-failure.timer

# Comprehensive boot diagnostics
shani-health --boot

See Also