feat(update): 0.3.5.1 → 0.4.0 in-place update flow#3399
Open
helix-nine wants to merge 9 commits into
Open
Conversation
b69a7ec to
4f0d1b0
Compare
4 tasks
Contributor
Author
✅ End-to-end validated on a VM via the real production update flowReplicated the actual 0.3.5.1 → 0.4.0 path as honestly as possible — the real Setup
The flow (all real 0.3.5.1 code)
Serial-console proof of the migrated boot: This exercises every piece of the device side:
No kernel panics, no failed mounts, no Still out of scope for this validation: physical hardware, UEFI/Secure-Boot variants, and Raspberry Pi (#3443). This was QEMU/KVM (SeaBIOS, single disk). The core logic is proven; a hardware pass is still wise before flipping the user-facing docs to OTA. |
The initramfs else-branch that boots a pre-0.4.0 disk layout was incomplete: it symlinked config/current.rootfs -> images/old.squashfs, a path nothing ever creates, so a migrated box failed to boot (dangling symlink -> exit 1). It also mis-selected the payload subvol as the rootfs to preserve. Rewrite it around the real state a 0.3.5.1 box is in after its (fixed, unchangeable) updater runs: an rsync payload in next/ carrying the 0.4.0 base image under next/images and the box's real machine-id / ssh / fstab in next/etc (the 0.3.5.1 updater copies those out of the live system during apply). Gate on a .startos-migration sentinel, carry the identity into config/overlay/etc, publish images/ via an atomic rename (crash can't leave an empty images dir), point current.rootfs at the shipped image, and drop the disposable old roots — only the data partition is preserved. Idempotent/crash-safe: a partial run re-runs or falls through to the normal-boot branch. First piece of the 0.3.5.1 -> 0.4.0 update feature.
Assemble the OTA squashfs the frozen Haskell registry loop-mounts and rsync-serves to legacy boxes. Base it on the real 0.3.5.1 rootfs (extracted from the published release ISO's live/filesystem.squashfs) so 0.3.5.1's rsync ships only the delta and its chroot update-grub2 runs in a complete userland; layer the 0.4.0 upgrade on top: the 0.4.0 base image as a nested images/<b3sum>.rootfs (naming matches fresh-install), the 0.4.0 kernel/initramfs under boot/, the .startos-migration sentinel, and our own update-grub2. migration-update-grub (shipped as the payload's /usr/sbin/update-grub2) deterministically writes grub.cfg for the 0.4.0 kernel/initramfs, reusing the box's real root= from /proc/cmdline instead of re-probing. Kernel/initrd names are recorded at assembly time so the retained 0.3.5.1 kernel in /boot can't be picked by mistake. x86 (.iso) wired; raspberrypi (.img.gz, no grub) is the remaining arch. Not yet run end-to-end (needs a real 0.3.5.1 box + the legacy registry).
Add `start-os-migration-squashfs`: fetches the published 0.3.5.1 image for the platform and runs assemble-migration-payload.sh to produce results/<basename>.migration.squashfs (the OTA payload uploaded to the frozen registry). Guards raspberrypi (its base is a .img.gz, not an .iso — wiring pending). Documents the in-place 0.3.5.1 -> 0.4.0 update path in the changelog. The user-facing update-040.md guide is left on the USB-reinstall flow until the OTA path is validated on hardware.
The 0.4.0 initramfs rewire lives in /etc/initramfs-tools/scripts/startos, selected by boot=startos on the kernel cmdline (debian/postinst sets GRUB_CMDLINE_LINUX 'boot=startos console=ttyS0,115200n8 console=tty0'). The migration update-grub2 was emitting root= alone, so the module would never run. Emit the 0.4.0 cmdline (boot=startos + consoles) while reusing the box's real root= from /proc/cmdline.
Add a Build Image step that runs start-os-migration-squashfs and uploads results/*.migration.squashfs as an artifact, for the platforms that shipped in 0.3.5.1 (x86_64/aarch64 + their -nonfree variants; no nvidia/riscv, and raspberrypi pending). Placed after the base uploads so the results/*.squashfs glob doesn't sweep the payload in. Ops pushes the artifact to the frozen registry via the existing OTA upload flow. Not exercised in CI yet.
Add .github/workflows/startos-migration-image.yaml: a standalone, dispatchable workflow that builds the 0.3.5.1 OTA payload by repackaging already-built artifacts — the 0.4.0 base squashfs from an ISO/SquashFS build run, and the legacy base from the 0.3.5.1 GitHub release ISO. Pure repackaging (no OS compile), so it runs on x86_64 for every platform; uploads <platform>.migration.squashfs for ops to publish to the frozen registry. Move the migration build out of startos-iso.yaml's image job into this dedicated workflow.
1dcfa3d to
6d02b24
Compare
Trigger the migration-image workflow via workflow_run when "Debian-based ISO and SquashFS" completes a successful non-PR run, repackaging each <platform>.squashfs artifact it produced (run id from the event). Keep workflow_dispatch as a manual fallback for re-running against a past ISO build by id.
8a92bfb to
b08fca0
Compare
Per decision, Raspberry Pi will not have an in-place 0.3.5.1 -> 0.4.0 update; it requires a USB/SD reflash. Reword the build guard + CI matrix comment accordingly (tracked in #3443).
Move the migration-image build from a separate workflow_run-triggered workflow into a migration-image job in startos-iso.yaml (needs: image). workflow_run only fires from the default branch, so the separate workflow never ran on the PR and effectively looked manual-dispatch-only. As a job it runs in the same ISO build, every time the image builds, consuming that run's <platform>.squashfs artifact + the 0.3.5.1 release ISO. Delete the now-redundant startos-migration-image.yaml.
dr-bonez
approved these changes
Jul 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
0.3.5.1 → 0.4.0 in-place update
Delivers the update path from StartOS 0.3.5.1 to 0.4.0 without the flaky, file-count-scaling full-rootfs rsync the 0.3.5.1 updater does today. We ride 0.3.5.1's existing (unchangeable) updater, but serve it a minimal payload — a single 0.4.0 base squashfs + kernel/initramfs + boot-config — and do the rootfs rewire in the 0.4.0 initramfs at next boot, before
/is mounted.The immovable channel (0.3.5.1,
core/startos/src/update/mod.rs@v0.3.5.1)do_updateis fixed Rust on already-shipped boxes; we can't change it. It:GET {marketplace}/eos/v0/latest→ version;{host}::{version}/{platform}/→/media/embassy/next/— this per-file rsync of a whole rootfs is the flaky step we're eliminating;fstab/machine-id/ ssh host keys intonext/etc;sync_boot: rsyncnext/boot → /boot, thenchroot next update-grub2(skipped onraspberrypi);touch config/upgrade→ reboot.The serving side is frozen — and needs no change
The endpoint a 0.3.5.1 box hits is the old Haskell registry (immutable). Its pipeline (
build/registry/{upload.cgi,resyncRsyncRegistry}@v0.3.5.1) accepts an uploaded.squashfs, loop-mounts it (mount $filename /srv/rsync/$version/$arch), and rsync-serves its contents as module[$version]. So the box rsyncs the tree inside whatever squashfs we publish intonext/. We conform by crafting the uploaded OTA squashfs — no registry change. Publishing uses the existing OTA upload flow, just with a differently-structured image for the0.4.0release.Design (3 stages)
do_update): the OTA squashfs we publish for0.4.0/<platform>contains, as its tree, the minimal scaffold —images/<hash>.rootfs(the real 0.4.0 base squashfs, one nested blob),boot/(0.4.0 kernel + initramfs), a.startos-migrationsentinel, minimaletc/, and our ownusr/sbin/update-grub2. rsync transfers ~a dozen files + one blob instead of ~40k files (the flakiness win). Then 0.3.5.1 execs ourupdate-grub2in the chroot, which deterministically writes a boot entry (root=PARTUUID=<embassyfs>, subvol-stripped) pointing at the 0.4.0 kernel+initramfs — no reliance on grub-mkconfig's probing (which the image build already has tosed-correct). Everything expensive happens pre-reboot; a failure safely leaves the box on 0.3.5.1.startos-initramfs-module): the else-branch detects the sentinel, carries the box's real identity out ofnext/etcintoconfig/overlay/etc, installs the shipped squashfs ascurrent.rootfs(atomicimages/publish), and drops the disposable old roots. Idempotent, crash-safe.version/chain,v0_3_6_alpha_0→v0_4_0_beta_N): replays the db/package/volume migration against/media/startos/data, loading the offlinemigration-imagesbaked into the base squashfs. The data partition is the only preserved state; neither stage 1 nor 2 touches it.No rollback (per @dr-bonez — emergency recovery is a thumb-drive reflash), so the boot swap is one-way; we optimize for reliability of the forward path.
Scope checklist
next/etc; atomic images publish; idempotent. Shell-sim verified.assemble-migration-payload.sh) — 0.3.5.1 base squashfs from the release ISO + nested 0.4.0 base image +boot/+ sentinel + ourupdate-grub2.migration-update-grub) — writes grub.cfg for the 0.4.0 kernel, reuses the box's realroot=, and emitsboot=startos(the param that selects the 0.4.0 initramfs module — bug caught + fixed).start-os-migration-squashfs.startos-migration-image.yamlworkflow — repackages an ISO build's<platform>.squashfsartifact onto the 0.3.5.1 release base and uploads<platform>.migration.squashfs(ops publishes it to the frozen registry). Pure repackaging, so it runs on x86_64 for all platforms.update-040.md) → OTA, deferred until hardware-validated.Testing boundary
End-to-end validated on a VM via the real
server.updateproduction flow — see the validation comment below. A live, set-up 0.3.5.1 box was updated through its owndo_update(rsync payload →next/,sync_boot→ chroot ourupdate-grub2, reboot), and the serial console confirms it booted 0.4.0-beta.10 from the migrated squashfs with the data pool + credentials preserved. Still not covered: physical hardware, UEFI/Secure-Boot variants, and Raspberry Pi (#3443) — so a hardware pass is still advisable before flipping the user guide to OTA.