Updates

Updater design

Source UPDATER_DESIGN.md Synced 2026-10-07 · b38e70a

On-disk layout (real hardware = one FAT boot volume; GBs, so two slots fit)#

/EFI/BOOT/BOOTX64.EFI        loader (single copy; updated rarely & carefully)
/BOOTSLOT                    tiny text marker the loader reads (see below)
/xeris_pubkey.ed25519        (optional; pubkey is also compiled into the OS)
/A/kernel.bin                slot A kernel
/A/data/...                  slot A payload  (CHROME.ELF, libs, paks, fonts)
/B/kernel.bin                slot B kernel
/B/data/...                  slot B payload

The loader reads /BOOTSLOT, loads /<slot>/kernel.bin, and passes the active slot to the kernel so it mounts /<slot>/data as "/".

QEMU testing note: one vvfat directory mount is FAT16-capped at ~504 MB, so two full ~400 MB payloads don't both fit in a single test mount. For testing we either (a) use small dummy payloads, or (b) mount slot A and slot B as two separate fat:rw: directories. On real hardware (FAT32, GBs) both slots coexist normally.

/BOOTSLOT format (line-based, loader- and kernel-parseable)#

active=A            # or B  -> which slot to boot
trial=1            # 1 = this slot is newly-updated and unconfirmed
tries=3            # boot attempts left before auto-rollback
  • Updater writes the new payload to the INACTIVE slot, verifies it, then sets active=<new>, trial=1, tries=3, and reboots.
  • Loader on a trial=1 slot decrements tries; if tries==0 it flips active back to the other (last-good) slot before booting — automatic rollback.
  • OS, once it boots far enough to be healthy (e.g. compositor up / first frame), writes trial=0 to COMMIT the slot. A slot that never commits rolls back.

Update bundle (produced by the signing tool, hosted on the update server)#

manifest.txt     signed manifest (below)
manifest.sig     Ed25519 signature (64 raw bytes) over manifest.txt bytes
<files...>       the payload files referenced by the manifest

manifest.txt (ASCII, line-based — trivial to parse in freestanding C)#

XUP1
version=1.2.3
slot=full            # kernel | data | full
file CHROME.ELF 305201152 <sha256hex>
file kernel.bin 1160000 <sha256hex>
file data/libc.so.6 ... <sha256hex>
...

Update flow (on-device updater)#

  1. Fetch manifest.txt + manifest.sig from the update server over HTTPS (reuse src/net/connection_mgr.c + http_client.c + the TLS client).
  2. Verify signature: Ed25519(pubkey embedded in OS) over manifest.txt bytes. Reject if invalid — this is the root of trust.
  3. Compare version to the running version; stop if not newer.
  4. Pick the inactive slot from /BOOTSLOT.
  5. Download each file to the inactive slot, verifying its SHA-256 vs the manifest as it lands (reject on mismatch). Write via fs_write_file.
  6. Once all files verified, set /BOOTSLOT: active=<inactive>, trial=1, tries=3.
  7. Reboot. Loader boots the new slot on trial; OS commits (trial=0) when healthy, else auto-rollback after tries failed boots.

Trigger#

  • On boot: the OS checks the server after startup; if a newer signed version exists, download+stage to the inactive slot and prompt/apply on next reboot.
  • Manual: a "check for updates" command/app runs the same path on demand.

Trust / keys#

  • Ed25519. Private key signs bundles on the build/release host; the 32-byte public key is compiled into the OS (xeris_update_pubkey.h) and also droppable as /xeris_pubkey.ed25519.
  • The keypair delivered now is a DEV key (generated in a sandbox). For production, regenerate the private key on a secure offline machine and re-embed the new public key before launch. Keep the private key OUT of the repo.

Components & status#

Component Where Status
Ed25519 keypair (dev) xeris_update_dev_private.pem (keep secret), ..._public.raw DONE
Public-key header xeris_update_pubkey.h (embed in OS) DONE
Host signing tool tools/xeris_update_sign.py DONE + tested (sign/verify/tamper)
Manifest format above DONE
On-device Ed25519 verify + SHA-512 (freestanding C) new src/net/xeris_ed25519.c NEXT (test vs reference)
On-device updater (fetch/verify/apply/slot-flip) new src/core/updater.c after crypto
Loader A/B + rollback boot/boot_uefi.c (read /BOOTSLOT, slot select, tries/rollback) after updater
Kernel: mount active slot's /data, commit trial on health syscall.c/mount after loader

Build integration (later)#

Add a make sign-update PAYLOAD=... VERSION=... target that runs the signer, and a make release-bundle that lays out A/B on the stick. Keep browser (CHROME.ELF+libs) as its own bundle (slot=data, frequent) separate from kernel.bin (slot=kernel, rare).

This page mirrors the file in the XerisOS repository. Changes go there. Edit on GitHub