Layer Wizard
A GUI installer wizard for permanently layering an RPM package onto a
bootc/image-based (read-only root) Linux system via rpm-ostree install,
with a guided reboot step and a one-click rollback if the result looks wrong.
Built for Universal Blue / Fedora bootc systems, where plain dnf install
refuses to run (the root filesystem is read-only) and the persistent
mechanism is rpm-ostree install <pkg>, which stages a new deployment that
only takes effect after a reboot.
What it does
- Welcome — shows the currently booted version/image.
- Package — enter a package name; "Check" confirms it resolves
(
dnf repoquery) before anything privileged happens. - Confirm — review the package and what's about to happen.
- Apply — runs
pkexec rpm-ostree install -y <package>, with the PolicyKit password prompt and a live streamed log. On success, offers Reboot Now / Reboot Later. - After rebooting, the app launches itself automatically and asks:
"Is everything working correctly?" — Yes, keep this update, or
No, something's wrong — roll back, which runs
pkexec bootc rollback --apply(reboots immediately into the previous deployment).
How the post-reboot check works
This system has no greenboot and no automated bad-boot detection
(systemd-bless-boot.service exists but isn't wired up to anything here) —
so the rollback offer is self-reported, not automatic. Two pieces make
that work:
-
Autostart entry. Right after a successful install,
ConfirmationMarker.installAutostart()(lib/confirmation_marker.dart) writes~/.config/autostart/com.ironmagma.layer_wizard.desktop, withExec=pointing at the currently-running binary (Platform.resolvedExecutable). KDE's session runs everything in~/.config/autostart/on login, which is what makes the app reappear on its own right after the reboot — you don't have to remember to open it. -
The check itself runs on every launch, not just via autostart.
lib/main.dart's_StartupRoutercallsConfirmationMarker.read()before deciding what to show:future: ConfirmationMarker.read(),builder: (context, snapshot) {final pending = snapshot.data;if (pending != null) return ConfirmationPage(pending: pending);return const WizardHomePage();}read()just checks whether~/.local/state/layer_wizard/pending_confirmation.jsonexists and parses it. That file is written by the wizard right after a successful install (ConfirmationMarker.write(...)inlib/wizard_page.dart), and deleted — along with the autostart entry — as soon as you click Keep or Roll back inlib/confirmation_page.dart.Running the check unconditionally at startup (rather than only when launched via autostart) is deliberate: if autostart doesn't fire for some reason, or you dismiss it and reopen the app later, you still land on the confirmation screen instead of that pending state getting silently dropped.
Project layout
lib/ostree_service.dart— all privileged/unprivileged shelling out:rpm-ostree status --json,dnf repoquery,pkexec rpm-ostree install,pkexec bootc rollback --apply,pkexec systemctl reboot.lib/confirmation_marker.dart— the pending-confirmation marker file and autostart.desktopentry (write/read/clear, install/remove).lib/wizard_page.dart— the install wizard (Stepper-based).lib/confirmation_page.dart— the post-reboot "did this work?" screen.lib/main.dart— startup routing between the two.
Privilege escalation follows the same pattern as the sibling
dnf-package-store
app: pkexec through the desktop PolicyKit agent, with stdout/stderr
streamed line-by-line into the UI.
Building
flutter build linux
Produces build/linux/x64/release/bundle/layer_wizard.