Confirmed on the test laptop's real install: /etc/snapper/configs/ was
completely empty post-install — snapper create-config failed silently and
BOS's advertised snapshot/rollback feature was entirely non-functional,
despite snapper-cleanup.timer being enabled and grub-btrfsd active (both
harmless no-ops with no config to act on).
Root cause is the known chroot-specific busy-mount race already documented
in this section's comments, but the existing recovery (retry umount 5x,
then one lazy-unmount fallback) wasn't sufficient on this hardware — a
lazy unmount detaches the mountpoint from the namespace immediately, but
whatever was holding it busy can take a moment longer to actually release,
and the immediately-following rmdir/create-config both fail if anything
still references /.snapshots at that instant.
Wrap the entire unmount → rmdir → create-config → cleanup → remount
sequence in an outer retry loop (checking whether the config file actually
exists before each attempt and after the loop), add a settle delay after
the lazy-unmount fallback, and turn the final failure into a loud ERROR
instead of a warning that's easy to miss — a system silently shipping
without snapshots is worse than one that's slow to set them up.
Verified against the actual cached mkinitcpio package (41-4), not assumed:
its shipped /etc/mkinitcpio.conf template is
HOOKS=(base systemd autodetect microcode modconf kms keyboard sd-vconsole
block filesystems fsck) — there's no "udev" token at all on a stock
install anymore, systemd is the base hook instead.
This silently broke two things:
- The existing plymouth-hook insertion (`sed 's/\budev\b/\0 plymouth/'`)
has been a no-op on every fresh install using a current mkinitcpio
package — the boot splash was never actually getting wired into the
initramfs, just failing quietly (the script's best-effort `|| echo WARN`
pattern doesn't catch a sed that "succeeds" by matching nothing).
- My own new encrypt-hook insertion from the previous commit had the same
flaw, and would have used the wrong hook regardless: `encrypt` is the
udev-based hook, systemd-based initramfs needs `sd-encrypt` instead.
post-install.sh now detects which base hook (systemd or udev) is actually
in HOOKS once, and uses the matching hook name for both plymouth
(unaffected by the choice, just needed the right anchor to insert after)
and LUKS unlocking (encrypt vs sd-encrypt — genuinely different hooks).
Also: avoid a redundant pacman -Sy in release-iso.yml's signing step —
the job already synced repos earlier in the same container.
yay (yay-bin, AUR-only like calamares/bibata) republished to [breadway] via
the same PKGBUILD + Forgejo workflow pattern, so users can reach the wider
AUR beyond bakery's bread ecosystem.
Disk encryption: Calamares' partition module already has LUKS support
enabled by default, but the checkbox led nowhere — no cryptsetup on the
live/target image, no mkinitcpio encrypt hook, no GRUB cryptodisk wiring.
An encrypted install would partition fine and then never boot. Added
cryptsetup, pinned luksGeneration to luks1 (GRUB doesn't support LUKS2 +
Argon2id), and post-install.sh now detects an encrypted root (lsblk TYPE
== crypt) and conditionally adds the encrypt hook + GRUB_ENABLE_CRYPTODISK +
--modules="cryptodisk luks luks2" on both grub-install passes. No effect on
a normal unencrypted install.
Secure Boot: self-signed via sbctl (shipped in packages.x86_64). BOS can't
ship a Microsoft-signed shim without going through Microsoft's own paid
UEFI CA process, so post-install.sh enrolls BOS's own keys automatically
only when the firmware is already in Setup Mode (sbctl status --json),
signs the kernel/bootloader, and leaves it alone otherwise — sbctl's own
pacman hook re-signs on every future kernel/GRUB update, no further
wiring needed.
Release signing: generated a dedicated Ed25519 "BOS Release Signing" key
(not reused from anything else), stored as the GPG_PRIVATE_KEY Forgejo
Actions secret. release-iso.yml now generates SHA256SUMS and a detached
SHA256SUMS.asc signature alongside every ISO upload; public key committed
at KEYS.asc with verification instructions in the README.
README updated: fixed a stale "greetd + tuigreet" line (breadgreet since
round 3), documented yay/encryption/secure-boot/verification.
Boot-critical (each confirmed with a real boot/install cycle, not just
code review):
- Live ISO's liveuser shell is zsh, but Hyprland autostart was written
to .bash_profile (never runs) — moved to .zprofile, and switched to
start-hyprland (Hyprland's own watchdog wrapper; raw `exec Hyprland`
is no longer the recommended launch method).
- copytoram (self-enables on most real hardware: non-optical boot +
image < 4GiB + enough free RAM) unmounts /run/archiso/bootmnt, which
unpackfs.conf and bos-copy-kernel both hardcoded as their source —
broke the installer outright on real hardware, confirmed by forcing
copytoram=y. Added a resolver step for unpackfs, and switched the
kernel copy to /usr/lib/modules/$(uname -r)/vmlinuz (part of the live
squashfs itself, unaffected by copytoram).
- BIOS installs got no bootloader — post-install.sh only ever ran the
UEFI grub-install path despite BOS shipping bios.syslinux. Added a
BIOS branch with disk auto-detection.
- @snapshots/@log/@cache were never real: iso/partition.conf's
btrfsSubvolumes key isn't part of this Calamares version's partition
module schema at all (same class of bug as the userShell fix below —
silently ignored). Calamares only natively creates @ and @home.
post-install.sh now creates the three subvolumes by hand after
unpackfs, migrates existing /var/log + /var/cache content into them
before mounting over, and adds the fstab entries — verified end to
end on real hardware, including grub-btrfs generating bootable
snapshot menu entries. The unmount step in the existing snapper
create-config dance also gained retry + lazy-unmount fallback after
a real chroot run hit a transient busy-mount race.
- Default shell was bash instead of zsh post-install: users.conf's
top-level `userShell` key isn't part of this Calamares version's
users module schema either — the real key is nested (user.shell).
- graphical-session.target ships RefuseManualStart=yes (systemd
convention), so the earlier attempt to activate it from hyprland.lua
silently failed and breadclipd (WantedBy=graphical-session.target)
never started. Starts breadclipd.service directly instead.
- /etc/os-release was never set (showed "Arch Linux"); live boot never
had quiet/splash/plymouth wired in (raw kernel scroll the whole
time) despite BOS already shipping a complete bread-logo+spinner
plymouth theme for the installed system.
Also: generalized build-local.sh's per-service skel baking (previously
only breadd.service was hand-committed; breadbox-sync/breadmill/
breadclipd never shipped), added the four new bakery packages to
BREAD_BINS, removed the redundant cliphist/fzf clipboard pipeline in
favor of breadclip, mirrored fastfetch's bread-logo config into skel,
and fixed a stale bos-update comment.
- post-install: ensure the `microcode` initramfs hook (after autodetect) so
installed systems carry CPU ucode — the live ISO embeds it, so nothing is
staged onto the target otherwise. Rebuild all presets with `mkinitcpio -P`.
- post-install: drop the nonexistent `sd-plymouth` hook branch; only the udev
`plymouth` hook exists. Set the theme then rebuild once.
- packages: add zathura + zathura-pdf-mupdf (BOS had no PDF viewer) and
libva-utils (`vainfo`); the Mesa VA-API backend now ships in `mesa` itself.
- bos-welcome: on first run, if NetworkManager isn't fully online, open nmtui
so the user connects before the first bos-update/pacman (avoids confusing DNS
errors on a fresh install). Float the bos-netsetup window like bos-welcome.
Fresh installs couldn't update — the live medium's /etc/pacman.d/gnupg doesn't
reliably carry to the target, so the first `pacman -Syu` failed with "keyring is
not writable / required key missing from keyring". Run pacman-key --init +
--populate archlinux in post-install so signature verification works out of the
box. ([breadway] is SigLevel=Never, so no extra key needed.)
Add the packages a general desktop is expected to ship, chosen to stay
opinionated but average-user friendly:
- Editors: neovim (+ ripgrep, fd for a usable nvim/fzf experience)
- GUI basics: gnome-text-editor, gnome-calculator, file-roller, loupe
- Media: vlc (BOS had codecs but no player)
- Hardware: cups + cups-pk-helper + system-config-printer (enable
cups.socket in post-install), blueman, seahorse
- Platform: qt5-wayland + qt6-wayland (native Wayland for Qt apps under
the QT_QPA_PLATFORM=wayland we set), xdg-desktop-portal-gtk (file
dialogs/screenshare for Flatpak/Electron/Zen), flatpak
- packages.x86_64: add bread, breadbar, breadbox, breadcrumbs, breadpad,
bos-settings so they ship in the squashfs and reach the target via unpackfs
(no network needed; install works fully offline)
- shellprocess.conf: set timeout 1800 — Calamares' 10s default was killing
post-install.sh partway (the real cause of the empty /boot + ESP); the "-"
prefix had been masking the kill as success
- bos-live-setup: live user now boots the real BOS desktop from /etc/skel
(breadd + breadbar + breadbox) with the installer layered on top
(auto-launch + Super+I), instead of an installer-only kiosk
- post-install.sh: drop the now-redundant networked `bakery install`
archiso keeps vmlinuz/initramfs in the ISO boot dir, not the squashfs, so
unpackfs lays down an empty /boot. The chroot's mkinitcpio/grub-mkconfig had
nothing to work with and the ESP ended up empty (firmware found no bootloader).
- shellprocess@kernel (dontChroot) copies the live kernel into the target
/boot before the bootloader step
- post-install.sh now runs grub-install itself, including a --removable pass
so firmware with no NVRAM entry still boots via EFI/BOOT/BOOTX64.EFI
unpackfs runs unsquashfs then rsync to copy the rootfs onto the target;
rsync was missing (error code 127), so add it alongside squashfs-tools.
unpackfs also copies the live filesystem verbatim, so the installed
system would inherit the archiso initramfs hooks (booting into the live
path) plus the live autologin/user/sudoers. Rework post-install.sh to run
in the target chroot as a resilient best-effort script that:
- removes the live autologin drop-in, bos-live-setup service/scripts and
the liveuser sudoers file, and locks root (sudo model; the live medium
left root passwordless),
- drops the archiso mkinitcpio config, installs the stock linux.preset and
regenerates the initramfs, then refreshes grub.cfg,
- keeps the snapper/services/dotfiles setup, with the network-dependent
bakery install made non-fatal so offline installs still complete.
ISO structural:
- Move post-install.sh → airootfs/etc/calamares/ (it was never in the squashfs)
- Create airootfs/etc/skel/.config/ with all dotfiles (deploy path now works)
- Add iso/pacman.conf with [breadway] custom repo stub for calamares + bakery
- Add Calamares branding component (bos/branding.desc + show.qml)
- Add missing unpackfs.conf and mount.conf modules
- Add live-session autostart: getty autologin → bash_profile → Hyprland → calamares
- Add polkit rule for wheel-group snapper rollback (pkexec path)
- Remove wlroots from packages (bundled with Hyprland); add bakery to package list
- Fix modules-search path in settings.conf
Dotfiles:
- Rename dotfiles/hyprland/ → dotfiles/hypr/ (Hyprland reads ~/.config/hypr/)
- Fix deprecated shadow options: drop_shadow/shadow_range → shadow { } block
bos-settings Rust:
- Replace glib::MainContext::channel (removed in glib 0.19) with async_channel
- Stream bakery update output line-by-line instead of buffering all at once
- Fix zombie processes: per-package update buttons now wait() in a thread
- Fix sidebar/stack mismatch at startup: select snapshots row to match default view
- Replace deprecated MessageDialog with AlertDialog (GTK 4.10+) throughout
- Use pkexec for snapper rollback so polkit handles privilege escalation
- Add confirmation dialog before delete snapshot (was missing, rollback had one)
- Add refresh button + repopulate after delete in snapshots view
- Add "Saved" / "Error: …" status label to every config view save button
- Add "Remove" buttons to breadbox contexts and breadcrumbs profiles
- Remove hardcoded model string from breadpad defaults
- Drop unused state mod; fix config_dir HOME fallback; fix zombie in editor launches
https://claude.ai/code/session_01WszGHvCmxgcyTwNSkfLF9P