Skip to content

System updates and rollback

A box runs unattended, so an update must never leave it broken. GurtOS does not update package by package. It replaces the whole system as one image, into a second copy of the system, and keeps the new one only if the box comes up healthy on it. This applies to boxes installed from the ISO.

The installer splits the disk into five partitions:

Partition Label Size Holds
BIOS boot box-bios 1 MB GRUB, for BIOS machines (x86 only)
EFI system BOX-ESP 512 MB GRUB, its menu, and grubenv, which says which slot starts
System slot A box-a 6 GB A whole system: kernel, Ubuntu, the box service
System slot B box-b 6 GB The other system. Empty until the first update.
Data box-data The rest Everything that must outlive an update

The box runs from one slot at a time. The other is the spare.

Disk layout and how an update is applied The disk has five partitions: BIOS boot (1 MB), the EFI system partition (512 MB), system slot A (6 GB), system slot B (6 GB) and the data partition (the rest). The box runs from one slot. An update checks its signature and hash, writes the other slot, and starts it once. The box must report healthy within 5 minutes. If it does, the new slot becomes the default. If not, the box restarts into the old slot. The disk (not to scale) BIOS 1 MB ESP 512 MB Slot A 6 GB, running Slot B 6 GB, the update Data: the rest log, workspaces, vault An update box-update apply checks signature, hash Writes the other slot the running one stays Starts it once next_entry Health check within 5 minutes Healthy the new slot becomes default Not healthy back to the old slot
The disk holds two system slots and a data partition. An update goes into the spare slot, starts once on trial, and stays only if the box is healthy.

Everything you care about is on the data partition and is bind-mounted into place at boot:

  • /var/lib/box: the action log, workspaces and task layers.
  • /etc/box: the vault and the box's identity key.
  • /var/lib/spacetimedb and /root/.config/spacetime: the live database and the identity that owns it.
  • /var/log/journal: system logs, so a failed update's logs are still there after the rollback.

An update also copies /etc/machine-id, SSH host keys on debug builds, and every task user (uids 30000 to 39999) into the new system, so a task resumed after the update keeps its uid.

Anything else changed by hand inside a running slot is lost at the next update. That is by design: the system is the image.

box-update apply <file or URL> does it in order:

  1. Fetch. It downloads root-<arch>.sqfs with its .sha256 and .sig files, or reads them from disk.
  2. Verify. It checks that .sig is a valid signature of the .sha256 file by the build key in /etc/box-os/update.pub, and that the image matches the hash. An image signed by any other key does not install.
  3. Write. It formats the spare slot and unpacks the image into it, then copies the identity and task users across.
  4. Arm a trial. It sets GRUB's next_entry to the spare slot. GRUB clears that value as it uses it, so the new slot starts exactly once. The default slot does not change yet.
  5. Restart.

At every start, box-confirm.service waits up to 5 minutes for the box service to answer GET /health with ok: true. The first start of a new system can take a while, because it publishes the SpacetimeDB module.

  • Healthy: the new slot becomes the default. The update is done.
  • Not healthy: the box restarts. GRUB starts the old slot, because the default never changed.
  • The new kernel panics: the system restarts by itself after 10 seconds (panic=10) and lands on the old slot the same way.

Either way, the result is written to /data/update/last and printed on the serial console, and box-update status shows it.

box-update rollback makes the other slot the default again and restarts into it. Use it when an update is healthy by the check but wrong for you. See update and roll back.

box-update status # which slot runs, what the other holds, the last result
box-update rollback # make the other slot the default again, and restart

The system in each slot is built from 40 named Ubuntu packages, 440 with their dependencies, plus the box service and the files GurtOS adds. It has no apt, no snapd, no cloud-init, no sudo, no desktop and no login prompt on any screen or serial port. Root's password is locked. Since nothing can install a package on the running system, the only way to change it is a new image.

These are the trade-offs in this release, written down:

  • The slots are plain writable ext4. They are not read-only or verified at boot. dm-verity is not built.
  • Updates are signed with one ed25519 key held on the build machine. There is no key rotation.
  • There is no rollback protection. An older signed image installs as readily as a newer one.
  • A system that hangs instead of panicking or failing its health check stays hung until someone restarts it. A hardware watchdog would cover that, and is not set up.
  • The GRUB menu on the EFI partition is written once at install. A change to the boot menu itself needs a reinstall.
  • The two slots cost 12 GB. The minimum disk is about 17 GB.
  • The boot loader is not signed, so Secure Boot must be off.

A box installed with core/install.sh on an existing Ubuntu machine has none of this. It updates like any Ubuntu machine, and the box service updates when you run the install script again. See install on Ubuntu.