Updates
Updater design
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=1slot decrementstries; iftries==0it flipsactiveback 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=0to 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)#
- Fetch
manifest.txt+manifest.sigfrom the update server over HTTPS (reusesrc/net/connection_mgr.c+http_client.c+ the TLS client). - Verify signature: Ed25519(pubkey embedded in OS) over manifest.txt bytes. Reject if invalid — this is the root of trust.
- Compare
versionto the running version; stop if not newer. - Pick the inactive slot from
/BOOTSLOT. - 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. - Once all files verified, set
/BOOTSLOT:active=<inactive>,trial=1,tries=3. - Reboot. Loader boots the new slot on trial; OS commits (
trial=0) when healthy, else auto-rollback aftertriesfailed 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