Atomic Updates
Shanios uses an intelligent multi-layered update system with automatic checking, user notifications, and the shani-deploy tool for atomic system updates. Updates are all-or-nothing — the running system is never touched while an update is being prepared.
Update Process Flow
- Check —
shani-updatetimer runs automatically (15 min after boot, then every 2 hours) and finds a new release - Notify — GUI dialog (yad/zenity/kdialog) prompts for user approval
- Download — R2 CDN primary, SourceForge fallback; resume support via aria2c/wget/curl
- Verify — SHA256 checksum + GPG signature; Btrfs snapshot of old slot taken as backup
- Deploy — New image extracted to inactive slot; UKI generated by
gen-efi; bootloader updated - Reboot — Reboot prompt shown; system boots into updated slot
- Confirm —
mark-boot-successwrites/data/boot-okoncemulti-user.targetis reached;bless-bootthen stops the boot counter
If boot fails, the system automatically falls back to the previous slot.
The Full Update Pipeline
Here is every step shani-deploy runs internally, in order.
1. Fetch and Verify
# Download version manifest from R2 CDN (with SourceForge fallback)
# Download image — streamed with resume support
# SHA256 verified after download
# GPG signature verified against known public key (7B927BFFD4A9EAAA8B666B77DE217F3DA8014792)
Nothing is written to the OS subvolumes until both the checksum and signature verify. A corrupted or tampered image never touches your system. If the download is interrupted, it resumes from where it stopped.
2. Snapshot the Inactive Slot
btrfs subvolume snapshot @green @green_backup_20260820153000
This safety snapshot (named <slot>_backup_<timestamp>) is created before any changes to the inactive slot, and only if that slot already holds a previous system (a first-ever deploy has nothing to back up). If extraction or the swap below fails partway, shani-deploy's error trap (restore_candidate) uses this snapshot to put @green back exactly as it was — the running @blue slot is never at risk either way.
3. Extract the New Image
# Decompress and receive the btrfs send stream into a scratch workspace
zstd -d --long=31 -T0 shanios-<version>-<profile>.zst -c | btrfs receive /mnt/temp_update
# Only once extraction succeeds: swap the candidate slot
btrfs property set -f -ts @green ro false
btrfs subvolume delete @green
btrfs subvolume snapshot /mnt/temp_update/shanios_base @green
btrfs property set -f -ts @green ro true
btrfs receive reconstructs the subvolume from the send stream into a scratch workspace first — @green itself is only deleted and replaced after the receive has fully succeeded, so a failed or interrupted extraction leaves the old @green (and the safety snapshot from step 2) untouched. The result is byte-for-byte identical to the subvolume that was snapshotted, signed, and shipped — not a package application or diff, but a complete verified reconstitution of exactly what passed build QA.
4. Generate the UKI
gen-efi generates and signs a new Unified Kernel Image for the updated slot, run inside a chroot of the candidate slot:
gen-efi configure green
The kernel cmdline embedded in the UKI is regenerated from the live disk state — current LUKS UUID, swap offset — and signed with the MOK keypair. The signed UKI is placed in the ESP and the bootloader is updated to set it as the next-boot default.
5. Boot Counting
The new entry gets a +3-0 suffix: 3 tries allowed, 0 done. Each failed boot attempt decrements the tries-left counter. If it reaches zero, systemd-boot automatically falls back to the previous slot's UKI.
If the new slot boots successfully, bless-boot calls bootctl set-good, stopping the countdown. The slot becomes the permanent default.
Before / After
Before update — running @blue:
@blue✅ Active (Booted) — current system@green⏸️ Inactive (Old) — update written hereshani-deploywrites new image into@green, keeps a Btrfs snapshot of old@green- Bootloader updated →
@greenset as next default
After reboot — running @green:
@blue⏸️ Inactive — instant rollback available@green✅ Active (Updated!) — new system runningshani-update --startup(run at login) checks for a fallback or candidate boot and confirms success
Automatic Rollback
Rollback actually happens in two stages — one automatic at the bootloader level, one confirmed by you at the login screen.
Stage 1 — systemd-boot falls back on its own. Every boot of the newly updated slot counts against its +3-0 tries. bless-boot only calls bootctl set-good (clearing the counter) after mark-boot-success has confirmed multi-user.target was reached and written /data/boot-ok. So any boot that crashes, hangs, or never reaches a working session — including a hard failure where the Btrfs root itself fails to mount, caught by a dracut hook that writes /data/boot_hard_failure before the mount is even attempted — leaves the counter un-cleared. After three such attempts, systemd-boot automatically boots the fallback slot's .conf entry instead. This part requires no user interaction and no login: you land on the previous, working system.
Stage 2 — the actual data rollback is offered, not silent. systemd-boot switching slots only changes which UKI boots next; it does not touch Btrfs subvolume contents, so the failed candidate slot is still sitting there broken. On next login, shani-update detects the mismatch between the booted slot and /data/current-slot (or the boot_hard_failure marker) and shows a "Roll Back Now / Ignore" dialog. Choosing to roll back runs shani-deploy --rollback, which restores the failed slot from its pre-deploy safety snapshot (see step 2 above) and rewrites both boot entries with no tries needed, since both slots are now known-good. No data on @home, @data, or any other persistent subvolume is touched by any of this — see Persistence Strategy.
The boot health pipeline behind stage 1:
mark-boot-in-progress— clears prior markers and plants/data/boot_in_progressat boot startmark-boot-success— writes/data/boot-okoncemulti-user.targetis reachedbless-boot— callsbootctl set-goodonce/data/boot-okexists, stopping the boot-count decrementcheck-boot-failure(15-minute timer) — if/data/boot_in_progressis still present and/data/boot-oknever appeared, records the slot in/data/boot_failure- a dracut pre-mount hook writes
/data/boot_hard_failureunconditionally before the root Btrfs mount is attempted, and a pre-pivot hook clears it on success — this catches failures too early for the above systemd units to ever run
Release Channels
shani-deploy supports two release channels:
| Channel | Cadence | Use Case |
|---|---|---|
stable | Monthly | Default — recommended for all users |
latest | More frequent | Early access, testing |
For channel switching commands, manual update commands, rollback, and storage management, see System Updates.
Storage Efficiency
The dual-slot architecture is not as expensive on disk as it sounds, for three concrete reasons rather than one fixed number:
- Every subvolume in
fstab(@root,@home,@data, and the@data/varlib/*bind-mount sources) mounts withcompress=zstd, so file content is transparently compressed on write. @blue/@greenare seeded as Btrfs snapshots of each other, not independent copies — Copy-on-Write means only blocks that actually differ between the two slots consume extra space; identical blocks are shared on disk.beesruns continuously in the background (beesd@<uuid>.service) deduplicating shared content across all subvolumes, on top of whatever CoW sharing already gives you for free.
There's no fixed "X% overhead" figure to quote here — actual savings depend on how much changes between updates and how compressible your data is — but the combination means a second slot rarely costs anywhere near a second full copy of the OS.