Boot Process
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:
| File | Slot |
|---|---|
/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:
| Hook | Phase | Priority | Purpose |
|---|---|---|---|
shanios-boot-failure-hook.sh | pre-mount | 90 | Writes /data/boot_hard_failure before root is mounted — persists if root mount fails |
shanios-overlay-etc.sh | pre-pivot | 50 | Mounts the /etc OverlayFS before pivot_root so systemd PID 1 sees user config from its first read |
shanios-boot-success-clear.sh | pre-pivot | 90 | Clears 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):
| Group | Dirs | Mount | Purpose |
|---|---|---|---|
CHROOT_BIND_DIRS | /dev /proc /sys /run /tmp | mount --rbind | Standard chroot plumbing so dracut, sbsign, mokutil, etc. work normally |
CHROOT_STATIC_DIRS | data etc var swap | mount --bind | Live 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/downloadsand the slot markers gen-efi may needetc—/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 stateswap— the shared@swapsubvolume, sogen-efican read the real swapfile'sresume_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)
+3-0— set on the new slot's EFI entry at deploy time (3 tries left, 0 done)- On each failed boot, the firmware decrements the tries-left counter
- At
0, systemd-boot falls back to the(Candidate)entry — before the OS even starts - Once
bless-boot.serviceruns 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)
| Service | When | What |
|---|---|---|
mark-boot-in-progress.service | local-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.service | multi-user.target, requires boot_in_progress to exist | Writes /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.service | multi-user.target, ConditionPathExists=/data/boot-ok | Runs bootctl set-good — stops the boot counter countdown |
check-boot-failure.timer / .service | OnBootSec=15m, fires once | If 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
| File | Location | Meaning |
|---|---|---|
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-cryptenrollbinds 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