Root cause of two panels rendering full-bleed instead of the 760px column (breadsearch, breadclip): hint()'s long unwrapped text reported an unbounded natural width, and CONTENT_MAX_WIDTH is a floor (set_size_request), not a cap — nothing was actually capping width. hint() and empty_state() now bound their labels with set_max_width_chars, which stabilizes every panel's column at a consistent width and left edge for the first time. service_control(): added a second status line (enabled-at-boot vs just-running), a confirm dialog on Stop for critical units (breadd — the whole desktop's event backbone was one accidental click from stopping, styled identically to optional helpers), and a consistent "Save only writes the file, Restart applies it" hint on panels that have both a service and a config file (previously only breadsearch said this, in its own inconsistent wording). Consistency sweep: Network's Scan button and empty-state treatment now match the rest of the app (was full-width where every other secondary action is auto-width; "not scanned" was a hint while "no results" was an empty_state card — now both empty_state). breadclip gets an actual "Open history" action instead of reading as an unfinished stub. breadbox's Save button pulled out of a mixed row into its own row like every other panel. Packages list rows now get the same card styling as every other row in the app. Panel title now shares view_scaffold's width+center treatment with the content column, so it actually heads the column instead of sitting ~440px to its left. Also: switch widgets had a stray box-shadow/outline/border showing as a faint ring around the knob, and bread-theme's switch slider color (@on-surface) was being masked by Adwaita's default gloss background-image — clearing it reveals the correctly-themed near-white knob against the dark surface. |
||
|---|---|---|
| .forgejo/workflows | ||
| assets | ||
| bos-settings | ||
| dotfiles | ||
| iso | ||
| packaging | ||
| scripts | ||
| .gitignore | ||
| bakery.toml | ||
| build-local.sh | ||
| Cargo.lock | ||
| Cargo.toml | ||
| DESIGN.md | ||
| LICENSE | ||
| README.md | ||
BOS — Bread Operating System
An Arch-based, Hyprland desktop distribution that ships the bread ecosystem preconfigured. One Calamares install produces a themed, bootable Wayland desktop — no manual Arch bootstrap, no wiring up dotfiles, no per-tool bakery installs.
Design rationale and the btrfs/A-B roadmap live in DESIGN.md. This file is the practical overview: what's in the image, how to build it, and how to test it.
What you get
- Compositor: Hyprland with a native-Lua config (
hyprland.lua), curated keybinds, snappy animations, blur, and pywal-driven colours on a black base. - bread ecosystem, baked into
/etc/skelfrom bakery-managed binaries (no network needed at install time):bread/breadd,breadbar(status bar- notification daemon),
breadbox(launcher),breadcrumbs(Wi-Fi profiles),breadpad(notes/reminders),breadman, and thebakerypackage manager.
- notification daemon),
- bos-settings: a GTK4 control panel that configures every bread* app's config from a GUI (non-destructively), plus snapshot rollback and bakery updates. See below.
- Login: greetd + tuigreet → Hyprland session.
- Boot splash: Plymouth
bostheme (logo + spinner, black background). - Theming: global dark across GTK3 (Adwaita-dark), GTK4/libadwaita
(
color-scheme: prefer-dark), and Qt (qt5ct/qt6ct Fusion dark); Papirus-Dark icons; Bibata cursor. - Apps: kitty, nautilus (+ gvfs), Zen browser, VLC, loupe, gnome-text-editor,
gnome-calculator, file-roller, with file associations wired in
mimeapps.list. - Hardware: pipewire audio, NetworkManager, BlueZ + blueman, CUPS printing with avahi mDNS discovery, TLP power management, fwupd firmware updates.
- Resilience: btrfs + snapper + snap-pac + grub-btrfs snapshots on every pacman transaction; zram swap; ufw firewall (deny-incoming, mDNS allowed).
Repo layout
bos/
├── Cargo.toml # workspace (members: bos-settings)
├── bos-settings/ # GTK4 unified settings app (Rust)
│ └── src/
│ ├── config/mod.rs # non-destructive toml_edit config layer
│ └── ui/{widgets,window,sidebar}.rs, ui/views/*.rs
├── iso/ # archiso profile
│ ├── profiledef.sh
│ ├── packages.x86_64 # live + installed package set
│ └── airootfs/ # files overlaid onto the image
│ └── etc/
│ ├── skel/ # default user dotfiles (hypr, kitty, gtk, …)
│ └── calamares/ # installer config + post-install.sh
├── packaging/ # in-house PKGBUILDs for AUR-only deps
│ ├── arch/ # bos-settings
│ ├── calamares/
│ └── bibata/
├── .forgejo/workflows/ # CI: build + publish packages to [breadway]
├── build-local.sh # native ISO build for this machine
└── DESIGN.md
Building the ISO
build-local.sh builds the image natively (no container) and bakes this
machine's bakery-installed bread binaries into /etc/skel:
sudo ./build-local.sh # release-quality (xz squashfs)
sudo FAST_BUILD=1 ./build-local.sh # fast dev iteration (zstd squashfs)
The ISO lands in out/bos-<date>-x86_64.iso. The script pins
SOURCE_DATE_EPOCH (reproducible UUIDs) and rewrites the [breadway] repo URL
to the Tailscale-reachable Forgejo registry for the build.
Why some packages are in-house
calamares, zen-browser-bin, and bibata-cursor-theme are AUR-only. BOS
keeps a PKGBUILD for each under packaging/ and republishes the built package
to the [breadway] repo via a Forgejo Actions workflow (built on the hestia
self-hosted runner, published with a scoped registry token). bos-settings
itself publishes the same way on a v* tag.
Testing in a VM
A reusable, GPU-accelerated launcher lives at ~/bos-vm/run.sh:
~/bos-vm/run.sh install # boot the ISO installer (target disk attached)
~/bos-vm/run.sh # boot the installed system from the disk
It uses KVM + -cpu host, 8 GiB / 8 vCPU, and virtio-vga-gl with
-display gtk,gl=on (virgl) — 3D acceleration is essential for a smooth
Hyprland session in QEMU. The disk lives on NVMe (not the tmpfs /tmp) to
avoid memory pressure.
bos-settings
bos-settings edits each bread* app's TOML non-destructively: it parses
the file with toml_edit, changes only the keys a view exposes, and writes it
back — preserving comments and any keys the UI doesn't model (calendar
passwords, saved-network passwords, model paths). Views:
| View | Config |
|---|---|
| bread | bread/breadd.toml — daemon, lua, modules, all adapters, events, notifications |
| breadbar | breadbar/style.css override |
| breadbox | breadbox/config.toml — launcher contexts |
| breadcrumbs | breadcrumbs/breadcrumbs.toml — settings, saved networks, profiles |
| breadpad | breadpad/breadpad.toml — settings, model + ollama, reminders, calendar |
| Snapshots | snapper list / rollback / delete |
| Packages | bakery installed list + updates |
| Hyprland | open config in editor + monitor list |
Build standalone:
cargo build --release -p bos-settings
cargo test -p bos-settings # includes config round-trip tests
The bread ecosystem at a glance
| Tool | Role | Launch |
|---|---|---|
bread / breadd |
Reactive automation daemon — normalises hardware/compositor signals into events dispatched to Lua modules | runs at login |
breadbar |
Top status bar (workspaces, clock, stats, tray) and the notification daemon | runs at login |
breadbox |
Application launcher | SUPER+Space |
breadpad |
Notes & reminders (AI-classified, optional CalDAV sync) | SUPER+U |
breadman |
Package-manager UI | SUPER+M |
breadcrumbs |
Wi-Fi profile state machine (location-aware) | CLI / BOS Settings |
bakery |
CLI package manager for the ecosystem | bakery |
bos-settings |
Unified GTK4 control panel for all of the above + snapshots + updates | SUPER+, |
Keyboard shortcuts
SUPER is the Windows/Cmd key. Press SUPER+/ at any time for this
cheatsheet in-session; first boot shows a short welcome (once).
| Keys | Action |
|---|---|
SUPER+Return |
Terminal (kitty) |
SUPER+Space |
App launcher (breadbox) |
SUPER+E / SUPER+B |
Files (nautilus) / Browser (zen) |
SUPER+U / SUPER+M |
breadpad / breadman |
SUPER+, / SUPER+/ |
BOS Settings / keybind cheatsheet |
SUPER+L / SUPER+N |
Lock / log out |
SUPER+Backspace |
Close window |
SUPER+F / SUPER+V / SUPER+T |
Fullscreen / float / toggle split |
SUPER+Shift+V |
Clipboard history |
SUPER+Tab |
Last window |
SUPER+Shift+S/C/P |
Screenshot region→file / region→clipboard / screen→file |
SUPER+arrows |
Move focus |
SUPER+Shift+h/j/k/l |
Move window |
SUPER+Shift+arrows |
Resize window |
SUPER+1..0 |
Switch to workspace 1–10 |
SUPER+Shift+1..0 |
Move window to workspace |
SUPER+[ / ] |
Previous / next workspace |
SUPER+left/right-drag |
Move / resize window with the mouse |
Known limitations
- GPUs: ships the generic Mesa stack — AMD and Intel work out of the box.
The NVIDIA proprietary driver is not included; NVIDIA users must install
nvidia/nvidia-utilsand set the usual Hyprland env vars after install. - Virtual machines: Hyprland needs GPU acceleration to be smooth. Use
virtio-vga-gl+-display gtk,gl=on(virgl); plain software rendering is noticeably laggy. - Wayland-first: X11-only apps run through XWayland; a few may misbehave.
- Secure Boot: not configured. Boot with Secure Boot disabled, or enroll
your own keys. The installer writes both an NVRAM entry and the removable
EFI/BOOT/BOOTX64.EFIfallback. - Snapshots assume btrfs: the snapper/grub-btrfs tooling expects the default btrfs subvolume layout the installer creates.
Recovery
An update broke something (system still boots): open BOS Settings →
Snapshots and roll back, or pick a pre-update snapshot from the GRUB
“snapshots” submenu at boot, then run snapper rollback from the booted
snapshot.
The system won't boot (broken GRUB / lost EFI entry):
- Boot the BOS ISO and open a terminal (
SUPER+Return). - Mount the installed root and EFI, then chroot:
mount -o subvol=@ /dev/sdXN /mnt mount /dev/sdXP /mnt/boot/efi # the EFI partition arch-chroot /mnt - Reinstall the bootloader (the same sequence the installer uses):
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=BOS --recheck grub-install --target=x86_64-efi --efi-directory=/boot/efi --removable --recheck grub-mkconfig -o /boot/grub/grub.cfg
Firmware shows “no boot device”: select EFI/BOOT/BOOTX64.EFI from the
firmware boot menu — the installer always writes that removable fallback.
Boot architecture notes
archiso keeps the kernel and initramfs outside the squashfs, so the installer
stages them explicitly: a shellprocess@kernel step copies the kernel + ucode
into the target /boot and writes a stock mkinitcpio preset before the native
initcpio module builds the initramfs. GRUB is not installed by Calamares'
bootloader/grubcfg modules (they leave the ESP empty in this layout) —
post-install.sh runs grub-install (NVRAM and --removable) +
grub-mkconfig instead, which is the sequence verified to boot.