Compare commits

..

64 commits
v0.2.0 ... main

Author SHA1 Message Date
Breadway
4edd356151 CI: set prerelease=false for release tags
Some checks failed
Mirror to GitHub / mirror (push) Successful in 6s
Build and publish package / package (push) Failing after 2m47s
Build and release ISO / release-iso (push) Successful in 16m24s
2026-06-19 07:11:22 +08:00
Breadway
4250b4be0e fix: use dl_url not github_url for bakery binary downloads
github_url points to GitHub releases that aren't always published for
dev/patch versions (bread v0.6.4 exists on dl.breadway.dev but not on
GitHub). dl.breadway.dev is publicly accessible and is the canonical
source — use dl_url from the bakery index instead.
2026-06-18 21:03:33 +08:00
Breadway
09a2c098ef fix: workflow YAML broken by blank lines inside --notes string
Blank lines inside a shell double-quoted string within a YAML literal
block cause the YAML parser to end the block early. Write the release
notes to a temp file with printf and pass --notes-file instead.
2026-06-18 21:00:24 +08:00
Breadway
6e82bf25ff CI: add ISO build + release workflow
release-iso.yml runs on v* tag pushes (or workflow_dispatch) on the hestia
self-hosted runner. It:
- Boots an archlinux container (--privileged --network=host)
- Downloads all bakery ecosystem binaries from their pinned GitHub releases
- Builds bread-theme from source at the tag in bos-settings/Cargo.toml
- Runs build-local.sh with CI_BUILD=1 + LAPTOP_HOME=/build-home
- Uploads the ISO to a Forgejo pre-release
- Creates a GitHub release pointing to Forgejo (GitHub 2 GB limit workaround)

build-local.sh: add CI_BUILD=1 mode — rewrites the [breadway] pacman repo
URL to localhost:3002 instead of the Tailscale address, since the CI
container runs on hestia with --network=host.
2026-06-18 20:54:46 +08:00
Breadway
86d41f231e v0.4.0: branding refresh, breadpaper baked in, bos-settings 0.4.0
- Move assets to assets/ directory (bread_white.svg, icons 256/512/1024px)
- Update Calamares branding + Plymouth theme logos
- Bake breadpaper (wallpaper manager + pywal) into /etc/skel alongside the
  rest of the bread ecosystem — previously missing from the ISO build
- Bump bos-settings to 0.4.0
2026-06-18 14:50:44 +08:00
Breadway
9a5af3ea8f Minor cleanups: gitignore out/, expect() messages, comment wording
- .gitignore: ignore the /out/ ISO build dir
- bos-settings: use expect() with messages over unwrap() for piped stdio;
  drop a stray blank line
- pacman.conf: reword the SigLevel=Never TODO as a future-improvement note
2026-06-17 22:57:58 +08:00
Breadway
6e85f812e4 ISO: microcode + plymouth hooks, PDF/VA-API packages, first-run network
- 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.
2026-06-17 22:57:58 +08:00
Breadway
514d0b900c Initialise the pacman keyring during install
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.)
2026-06-17 18:49:55 +08:00
Breadway
1ab6c7b188 Default to zsh distro-wide (live user + useradd default)
BOS shipped zsh + a p10k skel .zshrc and Calamares' userShell was already
/bin/zsh, but two paths still defaulted to bash:
  - /etc/default/useradd had SHELL=/usr/bin/bash, so any plain `useradd` (and
    anything not going through Calamares) created bash users.
  - bos-live-setup created the live ISO user with -s /bin/bash, so the live
    session ran bash instead of the BOS zsh setup.

Ship /etc/default/useradd with SHELL=/usr/bin/zsh and create liveuser with zsh
so the whole distro — live and installed — defaults to zsh.
2026-06-17 17:45:07 +08:00
Breadway
adc316fac6 Run breadd as a systemd user service by default
Best practice for the long-running bread daemon: ship an enabled user unit in
skel (~/.config/systemd/user/breadd.service + default.target.wants symlink)
instead of a bare Hyprland exec-once. Gives crash-restart, journald logging
(journalctl --user -u breadd), and proper lifecycle.

- ExecStart uses %h so it works for any account created from skel (not a
  hardcoded home).
- RuntimeDirectoryPreserve=yes so restarting breadd doesn't wipe the shared
  theme.css that bread-theme writes into /run/user/<uid>/bread.
- hyprland.lua: replace the `breadd` exec-once with a Wayland-env import
  (dbus-update-activation-environment) + `systemctl --user restart breadd`, so
  the service — which autostarts at login before Hyprland exists — picks up
  HYPRLAND_INSTANCE_SIGNATURE and can drive the compositor.
2026-06-17 14:47:58 +08:00
Breadway
0a6e220974 bos-settings 0.3.1: bread-theme v0.2.8 (working live reload)
Pick up the directory-watch fix so bos-settings hot-reloads the shared stylesheet
on `bread-theme reload` like the rest of the desktop (its v0.2.6 build had the
broken file-watch). No code change — only the dependency + version bump.
2026-06-17 13:59:59 +08:00
Breadway
82fb48cffa build-local: make WORK dir overridable (avoid /tmp tmpfs exhaustion)
On hermes /tmp is a 16 GB tmpfs; a full xz build can exhaust it mid-run. WORK now
honours an env override (matching OUT) so it can be pointed at the NVMe.
2026-06-17 09:03:45 +08:00
Breadway
aadda08797 Add bos-update + replicate the dev zsh shell
bos-update: one command that updates both BOS channels — pacman -Syu (snap-pac
snapshotted) and bakery update --all — best-effort so one failing doesn't abort
the other. Baked into the live env and skel.

Shell: match the dev laptop's zsh. Ship Powerlevel10k + zsh-autosuggestions,
zsh-history-substring-search and zsh-syntax-highlighting, sourced from the distro
packages (no oh-my-zsh framework) in the correct order, plus the dev .p10k.zsh.
Powerlevel10k is AUR-only, so it's republished to [breadway] via
packaging/powerlevel10k + a CI workflow (builds libgit2 + gitstatus from source),
same pattern as bibata / zen-browser-bin. skel/.zshrc keeps the BOS QoL aliases
and pywal palette import, with `update` aliased to bos-update.
2026-06-17 08:49:53 +08:00
Breadway
fbe9c9693e Add a copy-to-RAM boot entry (UEFI + BIOS)
Loads airootfs.sfs into RAM at boot so the installer reads from memory
instead of a possibly-flaky USB — fixes SquashFS read errors during
unpackfs. Kept as a separate menu entry (not default) since it needs a few
GB of RAM.
2026-06-16 19:40:16 +08:00
Breadway
aee05b814b bos-settings 0.3.0: shared theme release
Bump to 0.3.0 and pin bread-theme v0.2.6 in the lockfile so the [breadway]
package build (cargo --locked) picks up the shared-stylesheet migration.
2026-06-16 18:37:55 +08:00
Breadway
a3e14ba3a8 Ship a low-battery-warning bread module by default
A zero-config bread module (auto-discovered) that fires a critical
notification once when the battery runs low and resets on AC. No-op on
desktops. Demonstrates the bread automation layer out of the box.
2026-06-16 17:07:20 +08:00
Breadway
a1e3291a0c BOS: bake the bread-theme CLI and generate the shared stylesheet at login
- Add bread-theme to the binaries baked into /etc/skel from bakery state.
- Run `bread-theme generate` first in the Hyprland autostart so the shared
  GUI stylesheet ($XDG_RUNTIME_DIR/bread/theme.css) exists before breadbar /
  breadbox / bos-settings paint (they also live-reload it on change).
2026-06-16 16:59:03 +08:00
Breadway
7d422d78f3 bos-settings: use the shared bread-theme stylesheet
Replace the hardcoded Nord palette (which ignored pywal and the rest of the
ecosystem entirely) with bread_theme::gtk::apply_shared() — bos-settings now
loads the same generated stylesheet as breadbar/breadbox/breadpad and keeps
only its own layout rules (.view-content padding). It recolours live with the
desktop. Bump gtk4 0.9 -> 0.11 / glib -> 0.22 to match the ecosystem.

Note: bread-theme dep pins tag v0.2.6 (cut at release); Cargo.lock to be
regenerated then.
2026-06-16 16:47:52 +08:00
Breadway
f4043130ad docs+test: ecosystem matrix, keybinds, limitations, recovery, smoke test
README: add a bread-ecosystem feature matrix, keyboard-shortcut reference,
a Known Limitations section (NVIDIA/Mesa, VM GPU accel, Secure Boot, btrfs
assumption), and a Recovery guide (snapshot rollback + GRUB/EFI repair from
the live ISO).

scripts/smoke-test.sh: read-only post-install validator — btrfs subvolumes,
snapper config, enabled services, bread bins on PATH, bos-settings, default
dotfiles, and the GRUB EFI artifacts. Exits non-zero on any failure.
2026-06-16 15:51:47 +08:00
Breadway
1d7193773a Add first-run welcome + keybind cheatsheet onboarding
New users get a one-time welcome window on first boot (self-gating marker,
skipped for the live/installer user) and a keybind cheatsheet on SUPER+/.
Also bind BOS Settings to SUPER+, (it had no launcher bind). Both popups
are floated/centred via window rules. Addresses the onboarding/
discoverability gap from external review.
2026-06-16 15:51:47 +08:00
Breadway
569ba01550 Fix dark theme, animation speed, kitty opacity; add README
- libadwaita apps (nautilus, gnome-text-editor) rendered light because
  gsettings-desktop-schemas + dconf were missing, so the color-scheme
  prefer-dark autostart silently no-op'd. Add both packages.
- Replace Hyprland's slow default animations with the reference laptop's
  bezier curves + per-leaf speeds (hl.curve + hl.animation).
- kitty background_opacity 0.88 -> 0.6 to match the laptop; drop the
  macOS-only background_blur line (Hyprland supplies the blur).
- Add README.md documenting the actual image, build, and test flow.
2026-06-16 15:36:41 +08:00
Breadway
17e3e13e80 Use otf-font-awesome (desktop) instead of ttf-font-awesome
ttf-font-awesome resolved to woff2-font-awesome, a web-only format that
desktop apps can't render glyphs from. otf-font-awesome is the installable
desktop OTF.
2026-06-16 14:51:50 +08:00
Breadway
0457bac59a Complete the desktop: default apps, mDNS, firewall, zram, fonts
Wire up features that were half-shipped and add sensible resilience
defaults:

- mimeapps.list in skel: images->loupe, A/V->vlc, text->gnome-text-editor,
  pdf/html->zen, archives->file-roller, dirs->nautilus (so opening a file
  from nautilus actually does something)
- avahi + nss-mdns: CUPS network-printer discovery + .local resolution
  (enable avahi-daemon; insert mdns_minimal into nsswitch hosts:)
- ufw: deny-incoming firewall, mDNS (5353/udp) allowed so discovery still
  works; enabled in post-install
- zram-generator: compressed RAM swap (half RAM capped 4 GiB, zstd)
- fwupd + reflector.timer: firmware updates and periodic mirror refresh
- fonts: ttf-liberation (Office/web metric compat), ttf-dejavu, font-awesome
2026-06-16 14:47:06 +08:00
Breadway
04f31c409d bos-settings: full, non-destructive control of every bread* config
The bread/breadpad/breadcrumbs/breadbox views wrote invented schemas
(e.g. top-level log_level, [[profile]] name/ssids) that did not match the
apps' real TOML, so they showed empty and — worse — clobbered the real
config on Save, since the old config::save serialized only the keys it
modelled.

Rework the config layer onto toml_edit: parse each file into a
DocumentMut, mutate only the specific keys a view exposes, and write it
back preserving comments and any unmodelled keys (calendar password,
saved-network passwords, model paths). Unit-tested.

Add ui/widgets.rs (switch/entry/password/dropdown/spin/float/csv rows +
view scaffold + save button) bound to the shared document, then rewrite
the four views against the real schemas with far more coverage:

- bread: [daemon], [lua], [modules], all five [adapters.*] with their
  sub-options, [events], [notifications]
- breadpad: [settings], [model] + [model.ollama], [reminders], [calendar]
- breadcrumbs: [settings] (7 keys), [[networks]] editor, [profiles.*] editor
- breadbox: fixed to real [[contexts]] name/priority array editor

Goal: configure everything from the GUI rather than hand-editing TOML.
2026-06-16 14:26:49 +08:00
Breadway
e193bf26cf Fill desktop gaps: GUI apps, printing, media, Qt/portal integration
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
2026-06-16 14:26:49 +08:00
Breadway
82549286d2 Restore Bibata cursor now that it's published to [breadway]
bibata-cursor-theme-bin 2.0.7-1 is now in the [breadway] repo, so add it
back to the package list and re-enable the Bibata-Modern-Ice cursor in the
Hyprland env, GTK settings, and gsettings autostart.
2026-06-16 13:03:11 +08:00
Breadway
556c24a50a Republish bibata-cursor-theme to [breadway] (AUR-only upstream)
Bibata is the chosen BOS default cursor but is AUR-only, so mirror the
prebuilt -bin package into the [breadway] repo the same way calamares and
zen-browser-bin are. The workflow clones the triggering branch (not the
default branch) so it can build from iso-boot-fix, and uses the scoped
REGISTRY_TOKEN for publishing.
2026-06-16 13:01:38 +08:00
Breadway
e885488bd6 Drop bibata-cursor-theme (AUR-only, not in repos)
Use Hyprland default cursor instead. All other theming changes from the
previous commit are unaffected.
2026-06-16 10:34:10 +08:00
Breadway
d7acd251b4 Polish BOS: dark theme, shell QoL, icons, media, clipboard
- Global dark theme: gnome-themes-extra (GTK3 Adwaita-dark), qt5ct/qt6ct
  with Fusion dark skel config, QT_QPA_PLATFORMTHEME=qt5ct env
- Icons/cursor: papirus-icon-theme (Papirus-Dark) + bibata-cursor-theme
  (Bibata-Modern-Ice), set via gsettings autostart + GTK settings.ini + env
- gnome-keyring: credential storage for browsers and apps
- gvfs + gvfs-mtp: nautilus trash, phone access, network shares
- zsh: default user shell (users.conf userShell=/bin/zsh) + skel .zshrc
  with eza/bat/fzf/zoxide aliases, git prompt, fzf history search
- Shell QoL packages: eza, bat, fzf, zoxide
- Media: gst-plugins-good/bad/ugly, pavucontrol
- cliphist: clipboard history daemon (autostart) + SUPER+SHIFT+V bind
- Fonts: noto-fonts-cjk, ttf-jetbrains-mono-nerd
2026-06-16 10:31:18 +08:00
Breadway
f0a050fdc5 Make Plymouth splash black to match the black-base theme
The boot splash still used the old bread-brown background (#230b00)
after the rest of the theme moved to a black base (#0c0c0c). Switch
bos.script's background to black so the boot splash is consistent with
the wallpaper/pywal palette and breadbar.
2026-06-16 09:17:10 +08:00
Breadway
f8ae8fe125 Make BOS a complete, bootable, themed desktop OS
Install/boot reliability:
- Use native Calamares initcpiocfg/initcpio + explicit grub-install (nvram +
  --removable) in post-install; drop the flaky native bootloader/grubcfg modules.
- mount.conf: bind /proc /sys /dev (devtmpfs) /run + efivars into the chroot.
- bos-copy-kernel: stage kernel + write a stock mkinitcpio preset (replace the
  archiso preset). Per-service systemctl enable (fixes NetworkManager et al.
  silently not enabling due to the all-or-nothing grub-btrfs.path name).

System completeness:
- greetd + tuigreet graphical login; installed pacman.conf + working mirrorlist;
  base CLI tools (nano, micro, vim, htop, …); amd/intel-ucode; tlp + hypridle
  power management; systemd-timesyncd, fstrim.timer; wpa_supplicant wifi; Zen
  browser (republished to the [Breadway] repo).

Desktop + theming:
- Native Lua Hyprland config (hyprland.lua) with curated standard binds; kitty
  (blur) replaces foot; awww wallpaper + pywal palette (tamed to a black base
  with warm accents); GTK dark mode.
- Plymouth boot splash (bos theme: logo + spinner + status) via plymouthcfg.
- Varela Round font; Calamares bread-palette sidebar (logo/black-region polish
  still pending).
2026-06-16 09:09:34 +08:00
Breadway
787cc0e4c5 Fix breadd skel config schema; remove temp live diagnostic
- breadd.toml: the shipped skel used a stale [adapters] schema
  (keyboard/mouse/touchpad/gamepad booleans); breadd 0.6.4 expects
  hyprland/udev/power/network/bluetooth structs. `bluetooth = true` collided
  with the real AdapterToggle field and aborted the daemon at startup.
- Drop the temporary bos-live-diag serial diagnostic now that the live-session
  failures are diagnosed.
2026-06-14 19:38:06 +08:00
Breadway
76252f20b8 TEMP: route live diag to serial via sudo (revert after) 2026-06-14 19:17:46 +08:00
Breadway
ecd0fcda7a TEMP: live-session diagnostic to serial (revert after) 2026-06-14 19:08:42 +08:00
Breadway
a16245e7c5 Drop removed Hyprland dwindle:pseudotile option
Current Hyprland no longer accepts dwindle:pseudotile (it's a dispatcher now),
which threw a non-fatal config-error banner on both the live and installed
desktop. preserve_split is still valid and kept.
2026-06-14 18:54:06 +08:00
Breadway
105b67bb4d Bake bread ecosystem into the ISO + full live desktop; fix installer timeout
- 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`
2026-06-14 18:41:59 +08:00
Breadway
078c5f4f94 Fix unbootable installs: lay the kernel into the target and own GRUB
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
2026-06-14 17:57:50 +08:00
Breadway
2116b7cd7b Add rsync and make the installed system bootable/clean
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.
2026-06-14 13:29:49 +08:00
Breadway
8aebfc26c4 Add squashfs-tools so Calamares can unpack the rootfs
Calamares' unpackfs module shells out to unsquashfs to extract
airootfs.sfs onto the target. squashfs-tools wasn't in the live package
list, so installs failed at the Finish step with "Failed to find
unsquashfs ... Bad unpackfs configuration". Add it.
2026-06-14 13:15:27 +08:00
Breadway
08855ecd86 Log the live Hyprland session to a user-writable path
liveuser can't write /var/log, so the .bash_profile redirect
(Hyprland &>/var/log/hyprland-live.log) failed and bash aborted the line
without ever launching the compositor. Log to /tmp/hyprland-live.log,
which the live user can write.
2026-06-14 04:24:52 +08:00
Breadway
937a31732b Run the live session as an unprivileged user (Hyprland won't run as root)
The live medium autologged root on tty1 and exec'd Hyprland, but Hyprland
refuses to start with superuser privileges ("launched with superuser
privileges, but the privileges check is not omitted") and exited before
even creating a log — leaving tty1 at a blank blinking cursor. (Boot,
switch-root, firstboot suppression and the bos login on other ttys were
all already working.)

Adopt the standard live-ISO pattern:
- bos-live-setup.service (oneshot, gated on the archisobasedir cmdline so
  it only runs on the live medium) creates an unprivileged `liveuser`,
  adds it to the usual hardware groups, clears its password, and drops in
  a minimal live Hyprland config that auto-launches the installer.
- tty1 autologin now targets liveuser instead of root.
- Calamares needs root, so bos-launch-calamares runs it via passwordless
  sudo (/etc/sudoers.d/99-bos-live) with the Wayland env preserved, so the
  root installer renders on the live user's compositor.
2026-06-14 04:13:10 +08:00
Breadway
80e8efc84e Capture live-session Hyprland output and fall back to a shell
Redirect the live autologin compositor's stdout/stderr to
/var/log/hyprland-live.log, and on exit drop to an interactive shell
showing the return code instead of letting the getty autologin
respawn-loop hide any startup failure behind a blank blinking cursor.
Makes a failed live boot diagnosable and leaves the medium usable.
2026-06-14 03:57:27 +08:00
Breadway
f967422d61 Let the live Hyprland session fall back to software rendering
On GPU-less targets (VMs, headless, exotic hardware) wlroots refuses to
initialise without a hardware renderer, so the autologin session exec'd
Hyprland on tty1 and it died immediately — leaving a blinking cursor and
no desktop, while tty2 still showed the (correct) `bos` login.

Export WLR_RENDERER_ALLOW_SOFTWARE=1 before exec Hyprland in root's
.bash_profile so wlroots may use the llvmpipe software renderer when no
GPU renderer exists. On real hardware the hardware renderer is still
chosen; this is purely a fallback. Also set WLR_NO_HARDWARE_CURSORS=1 so
the pointer isn't invisible in VMs. Both must be real env vars (read at
wlroots init), not Hyprland `env=` lines, which apply too late.
2026-06-14 03:35:23 +08:00
Breadway
10f9449272 Add live-environment config so the ISO boots straight to the session
The fixed initramfs boots into userspace, but systemd-firstboot
(ConditionFirstBoot=yes, --prompt-locale --prompt-keymap-auto
--prompt-timezone --prompt-root-password) then blocked the console
waiting for interactive input, and root was locked (no /etc/shadow),
so the live medium never reached the autologin getty + Hyprland.

Ship the same base files releng uses to satisfy firstboot and unlock
root for autologin:
- etc/locale.conf  (LANG=C.UTF-8)        -> no locale prompt
- etc/localtime    (-> UTC)              -> no timezone prompt
- etc/vconsole.conf (KEYMAP=us)          -> no keymap prompt
- etc/hostname     (bos)
- etc/shadow       (root unlocked, empty pw, perms 0400 via profiledef)
- etc/passwd       (root shell = bash; system users are appended by the
                    systemd-sysusers pacman hook during pacstrap)

The overlay is applied before pacstrap (mkarchiso _make_custom_airootfs
precedes _make_packages) and these are pacman backup files, so the
static passwd/shadow act as the base and package scriptlets add the
rest — no clobbering of polkitd/pipewire/etc. users.
2026-06-14 03:13:54 +08:00
Breadway
6b20163c92 Add archiso initramfs hooks so the live ISO can switch root
The profile shipped boot configs and the package list but lacked the
mkinitcpio archiso configuration, so mkarchiso built a stock initramfs
with no archiso hook. At boot the kernel honoured archisosearchuuid/
archisobasedir but nothing knew how to find and mount airootfs.sfs, so
switch-root failed and the live medium dropped to emergency mode.

Add the canonical releng pieces:
- airootfs/etc/mkinitcpio.conf.d/archiso.conf (HOOKS incl. archiso)
- airootfs/etc/mkinitcpio.d/linux.preset (builds initramfs-linux.img)
- mkinitcpio{,-archiso,-nfs-utils} in packages.x86_64
2026-06-14 02:55:53 +08:00
Breadway
159d14774e Add in-house Calamares package (AUR-only upstream)
Calamares isn't in Arch's official repos, so BOS vendors the PKGBUILD and
publishes a built package to the [breadway] repo. All its deps are official
(kpmcore, qt6-*, yaml-cpp). Also drop the nonexistent calamares-qt6 from the
package list (calamares 3.4.x is already Qt6).
2026-06-13 23:39:39 +08:00
Breadway
9f9a5db5cc Set [breadway] SigLevel=Never (Forgejo db key unavailable to pacman) 2026-06-13 23:34:51 +08:00
Breadway
47ec044cd6 Fix archiso bootmodes and add syslinux to package list
mkarchiso validation: bios.syslinux.mbr/eltorito and uefi-x64.* bootmodes
are deprecated -> use bios.syslinux + uefi.systemd-boot. syslinux must be
in the package list for the BIOS bootmode; add memtest86+/edk2-shell too.
2026-06-13 23:32:25 +08:00
Breadway
a11a063c12 Add bootloader configs to archiso profile (syslinux/efiboot/grub)
The profile declared syslinux + systemd-boot bootmodes but lacked the
required config directories, so mkarchiso would fail. Added from the
official releng profile, rebranded to Bread OS; %PLACEHOLDER% tokens are
substituted by mkarchiso at build time.
2026-06-13 23:03:54 +08:00
Breadway
0486f4c7c6 Disable debug package so the main package publishes correctly
makepkg's debug split produced a -debug pkg; the upload's head -1 could
grab it instead of the main package. !debug yields a single package.
2026-06-13 23:00:48 +08:00
Breadway
617aeb3d99 Remove accidentally-committed .claude agent state; gitignore it 2026-06-13 22:54:47 +08:00
Breadway
a71ecdcd0b Fix bos-settings compile errors and use REGISTRY_TOKEN for publishing
bos-settings was scaffolded but never compiled. Fixes:
- main.rs: import gtk4::prelude (connect_activate/run)
- window.rs: disambiguate WidgetExt::display(); drop unused GBox import
- hyprland.rs: Label has no set_monospace -> use the monospace CSS class
- theme.rs: drop unused prelude import

Also switch package.yml to secrets.REGISTRY_TOKEN (scoped write:package),
since the auto Actions token is not authorized for the owner registry.
2026-06-13 22:54:27 +08:00
Breadway
b34217d869 Disable LTO in PKGBUILD (vendored ring/mlua static libs vs makepkg -flto) 2026-06-13 17:06:53 +08:00
Breadway
be81e03c45 Regenerate Cargo.lock for bos-settings
The scaffolded lockfile was stale, so packaging builds with --locked failed.
Regenerated against current Cargo.toml (88 packages).
2026-06-13 16:59:30 +08:00
Breadway
ac84b6bb36 Add Calamares branding images from bread logo
- logo.png (productLogo/productIcon): rasterised from the bread logo, transparent
- languages.png (productWelcome): logo centred on a light Nord canvas
- logo.svg / bread_white.svg: source vector

Resolves the missing-branding-asset blocker so Calamares can render.
Colour scheme can be refined when final SVGs land.
2026-06-13 16:53:25 +08:00
Breadway
e8e33e35c4 Source calamares from official extra, not [breadway]
calamares and calamares-qt6 are in Arch's extra repo; no custom PKGBUILD
needed. Update packages.x86_64 and the pacman.conf comment accordingly.
2026-06-13 16:40:59 +08:00
Breadway
7d7737c3b0 Clone from public URL, not GITHUB_SERVER_URL (resolves to localhost in runner)
The Forgejo runner injects GITHUB_SERVER_URL as http://localhost:3002, which
is unreachable from inside the job container. Use the public URL instead.
2026-06-13 16:14:12 +08:00
Breadway
8838cc35f2 Rename mirror secret to MIRROR_TOKEN (GITHUB_ prefix is reserved)
Forgejo/gitea rejects user secret names starting with GITHUB_.
2026-06-13 16:10:39 +08:00
Breadway
11e27a0723 Use Forgejo-prescribed pacman section name for the Arch registry
Forgejo serves the repo db as {owner}.{group}.{domain}.db, and pacman
fetches "<section>.db" from Server — so the section name must match.
2026-06-13 16:03:55 +08:00
Breadway
267f6df523 Fix Forgejo workflows for the actual server capabilities
- package.yml: use correct Arch registry upload (octet-stream + binary body
  + PUT /api/packages/Breadway/arch/os), drop --privileged, remove
  actions/checkout (archlinux image has no Node) in favour of a manual
  shell clone, use the built-in Actions token instead of a stored secret,
  and --nocheck (tests belong in CI, not packaging)
- mirror.yml: clone --mirror + explicit refs/heads + refs/tags push with
  --prune, instead of pushing refs/remotes pollution from a checkout
- pacman.conf: correct Server URL to the Forgejo Arch registry format

Requires only the GITHUB_MIRROR_TOKEN secret (GitHub PAT, repo scope) for
the mirror job; package publishing uses the automatic per-run token.
2026-06-13 16:01:50 +08:00
Breadway
baff024016 Add Forgejo Actions workflows and fix [breadway] repo URL
- .forgejo/workflows/mirror.yml: mirrors every push/tag to GitHub
- .forgejo/workflows/package.yml: builds PKGBUILD on tag and publishes
  bos-settings to the Forgejo Arch package registry (distrib=breadway)
- iso/pacman.conf: replace placeholder repo.breadway.dev with the actual
  Forgejo package registry URL

Requires two Forgejo secrets:
  GITHUB_MIRROR_TOKEN — GitHub PAT with repo push scope
  FORGEJO_TOKEN       — Forgejo token with package:write scope
2026-06-13 11:42:00 +08:00
Breadway
a028e7462a Add bakery.toml and packaging/arch to match bread ecosystem
Mirrors the build/distribution pattern used by the bread project:
- bakery.toml describes bos-settings as a bakery-managed package
- packaging/arch/PKGBUILD builds and installs the binary via cargo
- packaging/arch/bos-settings.desktop for app launchers
- LICENSE (MIT) required by PKGBUILD
2026-06-13 11:32:40 +08:00
Breadway
e67e2a2f66 Fix prod-readiness issues flagged in audit
- Fix XDG config dir logic in config/mod.rs (was double-nesting and had /home/user hardcode)
- Replace /home/user hardcodes in breadbar.rs and hyprland.rs with config::config_dir()
- Fix /home/user hardcode in packages.rs (uses /root fallback for .local/state path)
- Remove eprintln! from GTK callback in packages.rs (no stderr at runtime)
- Fix YAML parse error in branding.desc (missing space after sidebarTextHighlight key)
- Add .gitignore (Rust target/, ISO artifacts, editor/OS junk, secrets)
- Delete state.rs (dead code — never mod'd in main.rs)
- Add brightnessctl, grim, slurp to packages.x86_64 (used by keybinds)
- Rename can-you-begin-a-composed-beacon.md → DESIGN.md
2026-06-13 11:29:53 +08:00
Breadway
8a1157dfce Merge pull request #1 from Breadway/scaffold/bos-initial
Scaffold/bos initial
2026-06-13 11:15:38 +08:00
41 changed files with 3122 additions and 187 deletions

View file

@ -0,0 +1,35 @@
name: Build and publish powerlevel10k
# Powerlevel10k (the BOS default zsh prompt) is AUR-only, so BOS maintains an
# in-house PKGBUILD and publishes the built package to the [breadway] repo.
# Builds gitstatus + libgit2 from source, so it needs cmake + zsh beyond base-devel.
on:
push:
paths:
- 'packaging/powerlevel10k/**'
workflow_dispatch:
jobs:
powerlevel10k:
runs-on: [self-hosted, hestia]
container:
image: archlinux:latest
steps:
- name: Build and publish
env:
PUBLISH_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -euo pipefail
pacman -Syu --noconfirm base-devel git cmake zsh
useradd -m builder
git config --global --add safe.directory '*'
git clone --depth 1 --branch "${GITHUB_REF_NAME}" \
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /home/builder/src
chown -R builder:builder /home/builder/src
su builder -c "cd /home/builder/src/packaging/powerlevel10k && makepkg -f --noconfirm --nocheck"
PKG=$(find /home/builder/src/packaging/powerlevel10k -name '*.pkg.tar.zst' | head -1)
curl -fsS -X PUT \
-H "Authorization: token ${PUBLISH_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${PKG}" \
"https://git.breadway.dev/api/packages/Breadway/arch/os"

View file

@ -0,0 +1,207 @@
name: Build and release ISO
# Builds the BOS ISO on the hestia self-hosted runner (native Arch container),
# downloads all bakery ecosystem binaries from their GitHub releases, compiles
# bread-theme from source, and uploads the resulting ISO to a Forgejo pre-release.
# A matching GitHub release is created that points to Forgejo for the download
# (GitHub releases cannot host files larger than 2 GB).
#
# Required secrets:
# RELEASE_TOKEN — Forgejo API token with write:repository scope
# MIRROR_TOKEN — GitHub personal access token with repo scope (already used by mirror.yml)
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
tag:
description: 'Git tag to build (e.g. v0.4.0)'
required: true
jobs:
release-iso:
runs-on: [self-hosted, hestia]
container:
image: archlinux:latest
# --privileged: mkarchiso needs CAP_SYS_ADMIN for loop mounts + mknod
# --network=host: gives localhost:3002 access to Forgejo (avoids the
# public git.breadway.dev → Aegis → Tailscale round-trip for pacman)
options: --privileged --network=host
steps:
- name: Install build dependencies
run: |
pacman -Syu --noconfirm archiso curl python git rust
- name: Determine tag and version
id: vars
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG="${{ github.ref_name }}"
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
- name: Clone repository at tag
run: |
git clone --branch "${{ steps.vars.outputs.tag }}" --depth 1 \
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /bos
- name: Download bakery ecosystem binaries
run: |
set -euo pipefail
mkdir -p /build-home/.local/bin \
/build-home/.local/state/bakery \
/build-home/.cache/bakery
# Fetch the canonical bakery index
curl -fsSL "https://dl.breadway.dev/index.json" \
-o /build-home/.cache/bakery/index.json
# Download each binary from dl.breadway.dev (canonical source; github_url
# is not always published for dev/patch releases) and generate the
# installed.json that bakery expects in ~/.local/state.
python3 << 'PYEOF'
import json, urllib.request, os
with open('/build-home/.cache/bakery/index.json') as f:
idx = json.load(f)
BIN_DIR = '/build-home/.local/bin'
installed = {}
for pkg_name, pkg in idx['packages'].items():
bins = []
for b in pkg['binaries']:
dest_name = b['name'].removesuffix('-x86_64')
dest = os.path.join(BIN_DIR, dest_name)
url = b['dl_url']
print(f' {dest_name} <- {url}', flush=True)
urllib.request.urlretrieve(url, dest)
os.chmod(dest, 0o755)
bins.append(dest_name)
# installed.json services field is a flat list of unit-name strings
services = [
(s['unit'] if isinstance(s, dict) else s)
for s in pkg.get('services', [])
]
installed[pkg_name] = {
'name': pkg_name,
'version': pkg['version'],
'binaries': bins,
'services': services,
'installed_at': '2024-01-01T00:00:00+00:00',
}
with open('/build-home/.local/state/bakery/installed.json', 'w') as f:
json.dump({'packages': installed}, f, indent=2)
print('installed.json written', flush=True)
PYEOF
- name: Build bread-theme from source
run: |
set -euo pipefail
# bread-theme is not in the bakery index; build it at the tag pinned
# in bos-settings/Cargo.toml so the CLI matches the library version.
THEME_TAG=$(grep 'bread-theme.*tag' /bos/bos-settings/Cargo.toml \
| grep -oP '"v[^"]+"' | tr -d '"')
echo "Building bread-theme @ $THEME_TAG"
git clone --branch "$THEME_TAG" --depth 1 \
https://github.com/Breadway/bread-ecosystem /bread-ecosystem
cd /bread-ecosystem
cargo build --release -p bread-theme
install -m 755 target/release/bread-theme /build-home/.local/bin/bread-theme
echo "bread-theme built OK"
- name: Build ISO
run: |
set -euo pipefail
mkdir -p /bos-work /bos-out
cd /bos
LAPTOP_HOME=/build-home \
WORK=/bos-work \
OUT=/bos-out \
CI_BUILD=1 \
bash build-local.sh
ls -lh /bos-out/*.iso
- name: Create Forgejo release and upload ISO
env:
FORGEJO_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
set -euo pipefail
TAG="${{ steps.vars.outputs.tag }}"
VERSION="${{ steps.vars.outputs.version }}"
ISO=$(ls /bos-out/*.iso | head -1)
ISO_NAME="bos-${VERSION}-x86_64.iso"
# Use an existing release for this tag if one exists (e.g. created
# manually or by a prior re-run), otherwise create a fresh one.
EXISTING=$(curl -sf \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" \
2>/dev/null || true)
RELEASE_ID=$(echo "${EXISTING}" | python3 -c \
"import json,sys; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
if [ -z "${RELEASE_ID}" ]; then
RELEASE=$(curl -fsS -X POST \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-H "Content-Type: application/json" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases" \
-d "{
\"tag_name\": \"${TAG}\",
\"name\": \"BOS ${TAG}\",
\"prerelease\": false,
\"body\": \"ISO image attached below.\\n\\nSee the [README](https://github.com/Breadway/bos#testing-in-a-vm) for VM testing instructions.\"
}")
RELEASE_ID=$(echo "${RELEASE}" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
fi
echo "Using release ID: ${RELEASE_ID}"
# Remove any existing asset with the same name before uploading
ASSET_ID=$(curl -sf \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets" \
| python3 -c "
import json,sys
assets=json.load(sys.stdin)
match=[a['id'] for a in assets if a['name']=='${ISO_NAME}']
print(match[0] if match else '')
" 2>/dev/null || true)
if [ -n "${ASSET_ID}" ]; then
curl -fsS -X DELETE \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
echo "Removed existing ${ISO_NAME} asset"
fi
curl -fsS -X POST \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-F "attachment=@${ISO};filename=${ISO_NAME}" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets"
echo "Uploaded: ${ISO_NAME}"
- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.MIRROR_TOKEN }}
run: |
set -euo pipefail
TAG="${{ steps.vars.outputs.tag }}"
VERSION="${{ steps.vars.outputs.version }}"
FORGEJO_URL="https://git.breadway.dev/${GITHUB_REPOSITORY}/releases/tag/${TAG}"
printf '**Download ISO:** %s\n\nGitHub releases cannot host files >2 GB; the `bos-%s-x86_64.iso` (~2.5 GB) is on Forgejo.\n\nSee the [README](https://github.com/Breadway/bos#testing-in-a-vm) for VM testing instructions.' \
"${FORGEJO_URL}" "${VERSION}" > /tmp/gh-release-notes.md
gh release create "${TAG}" \
--repo "Breadway/bos" \
--title "BOS ${TAG}" \
\
--notes-file /tmp/gh-release-notes.md \
2>/dev/null || echo "GitHub release already exists — skipping"

1
.gitignore vendored
View file

@ -27,6 +27,7 @@ secrets/
# archiso build artifacts (these are large and reproducible)
/iso-build/
/iso-out/
/out/
*.iso
*.img

277
Cargo.lock generated
View file

@ -28,9 +28,10 @@ checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "bos-settings"
version = "0.2.0"
version = "0.4.0"
dependencies = [
"async-channel",
"bread-theme",
"glib",
"gtk4",
"serde",
@ -39,11 +40,22 @@ dependencies = [
"toml_edit 0.22.27",
]
[[package]]
name = "bread-theme"
version = "0.2.3"
source = "git+https://github.com/Breadway/bread-ecosystem?tag=v0.2.8#77417d552130281ff787e07d52541eb25e9d533b"
dependencies = [
"dirs",
"gtk4",
"serde",
"serde_json",
]
[[package]]
name = "cairo-rs"
version = "0.20.12"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e3bd0f4e25afa9cabc157908d14eeef9067d6448c49414d17b3fb55f0eadd0"
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
dependencies = [
"bitflags",
"cairo-sys-rs",
@ -53,9 +65,9 @@ dependencies = [
[[package]]
name = "cairo-sys-rs"
version = "0.20.10"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "059cc746549898cbfd9a47754288e5a958756650ef4652bbb6c5f71a6bda4f8b"
checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54"
dependencies = [
"glib-sys",
"libc",
@ -72,6 +84,12 @@ dependencies = [
"target-lexicon",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -87,6 +105,27 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@ -188,9 +227,9 @@ dependencies = [
[[package]]
name = "gdk-pixbuf"
version = "0.20.10"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd242894c084f4beed508a56952750bce3e96e85eb68fdc153637daa163e10c"
checksum = "25f420376dbee041b2db374ce4573892a36222bb3f6c0c43e24f0d67eae9b646"
dependencies = [
"gdk-pixbuf-sys",
"gio",
@ -200,9 +239,9 @@ dependencies = [
[[package]]
name = "gdk-pixbuf-sys"
version = "0.20.10"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96"
checksum = "48f31b37b1fc4b48b54f6b91b7ef04c18e00b4585d98359dd7b998774bbd91fb"
dependencies = [
"gio-sys",
"glib-sys",
@ -213,9 +252,9 @@ dependencies = [
[[package]]
name = "gdk4"
version = "0.9.6"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60"
checksum = "fd42fdbbf48612c6e8f47c65fb92d2e8f39c25aecd6af047e83897c1a22d2a4e"
dependencies = [
"cairo-rs",
"gdk-pixbuf",
@ -228,9 +267,9 @@ dependencies = [
[[package]]
name = "gdk4-sys"
version = "0.9.6"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a"
checksum = "9d974ac4f15e67472c3a9728daf612590b4a5762a4b33f0edd298df0b80d043c"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@ -244,10 +283,21 @@ dependencies = [
]
[[package]]
name = "gio"
version = "0.20.12"
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e27e276e7b6b8d50f6376ee7769a71133e80d093bdc363bd0af71664228b831"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gio"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3848bcba3a35cc0a71df8ba8ecfd799d6bfb862342a53a4a915fb62213aa4e6"
dependencies = [
"futures-channel",
"futures-core",
@ -262,22 +312,22 @@ dependencies = [
[[package]]
name = "gio-sys"
version = "0.20.10"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83"
checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "glib"
version = "0.20.12"
version = "0.22.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffc4b6e352d4716d84d7dde562dd9aee2a7d48beb872dd9ece7f2d1515b2d683"
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
dependencies = [
"bitflags",
"futures-channel",
@ -296,12 +346,11 @@ dependencies = [
[[package]]
name = "glib-macros"
version = "0.20.12"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8084af62f09475a3f529b1629c10c429d7600ee1398ae12dd3bf175d74e7145"
checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19"
dependencies = [
"heck",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
@ -309,9 +358,9 @@ dependencies = [
[[package]]
name = "glib-sys"
version = "0.20.10"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215"
checksum = "5f7fbac234ed5bc2a28359b7bde8e1b9cdf1441cc2d7f068e4824672d7db9445"
dependencies = [
"libc",
"system-deps",
@ -319,9 +368,9 @@ dependencies = [
[[package]]
name = "gobject-sys"
version = "0.20.10"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda"
checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c"
dependencies = [
"glib-sys",
"libc",
@ -330,9 +379,9 @@ dependencies = [
[[package]]
name = "graphene-rs"
version = "0.20.10"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b86dfad7d14251c9acaf1de63bc8754b7e3b4e5b16777b6f5a748208fe9519b"
checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1"
dependencies = [
"glib",
"graphene-sys",
@ -341,9 +390,9 @@ dependencies = [
[[package]]
name = "graphene-sys"
version = "0.20.10"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df583a85ba2d5e15e1797e40d666057b28bc2f60a67c9c24145e6db2cc3861ea"
checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c"
dependencies = [
"glib-sys",
"libc",
@ -353,9 +402,9 @@ dependencies = [
[[package]]
name = "gsk4"
version = "0.9.6"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855"
checksum = "53c912dfcbd28acace5fc99c40bb9f25e1dcb73efb1f2608327f66a99acdcb62"
dependencies = [
"cairo-rs",
"gdk4",
@ -368,9 +417,9 @@ dependencies = [
[[package]]
name = "gsk4-sys"
version = "0.9.6"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc"
checksum = "d7d54bbc7a9d8b6ffe4f0c95eede15ccfb365c8bf521275abe6bcfb57b18fb8a"
dependencies = [
"cairo-sys-rs",
"gdk4-sys",
@ -384,9 +433,9 @@ dependencies = [
[[package]]
name = "gtk4"
version = "0.9.7"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f274dd0102c21c47bbfa8ebcb92d0464fab794a22fad6c3f3d5f165139a326d6"
checksum = "7181b837f04cbe93f79441475f7a00560a92cba7a72e38cc1a68b6f8b78eaae2"
dependencies = [
"cairo-rs",
"field-offset",
@ -405,9 +454,9 @@ dependencies = [
[[package]]
name = "gtk4-macros"
version = "0.9.5"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999"
checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@ -417,9 +466,9 @@ dependencies = [
[[package]]
name = "gtk4-sys"
version = "0.9.6"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6"
checksum = "20ba8e695e2640455561274e65e45f0a151619e450746007667f4b23ceae4e1b"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@ -468,6 +517,15 @@ version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libredox"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
dependencies = [
"libc",
]
[[package]]
name = "memchr"
version = "2.8.2"
@ -484,10 +542,16 @@ dependencies = [
]
[[package]]
name = "pango"
version = "0.20.12"
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6576b311f6df659397043a5fa8a021da8f72e34af180b44f7d57348de691ab5c"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "pango"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "251bdc6e6487b811be0e406a21e301e07e45c0aa8fa39e00c0c8e12a91752438"
dependencies = [
"gio",
"glib",
@ -497,9 +561,9 @@ dependencies = [
[[package]]
name = "pango-sys"
version = "0.20.10"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186909673fc09be354555c302c0b3dcf753cd9fa08dcb8077fa663c80fb243fa"
checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6"
dependencies = [
"glib-sys",
"gobject-sys",
@ -552,6 +616,17 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
@ -670,6 +745,26 @@ version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.8.23"
@ -774,13 +869,43 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
@ -789,28 +914,46 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@ -823,24 +966,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"

209
README.md Normal file
View file

@ -0,0 +1,209 @@
# BOS — Bread Operating System
An Arch-based, Hyprland desktop distribution that ships the [bread
ecosystem](https://github.com/Breadway) 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](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/skel` from 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 the `bakery` package manager.
- **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 `bos` theme (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`:
```sh
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`:
```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:
```sh
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 110 |
| `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-utils` and 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.EFI` fallback.
- **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):**
1. Boot the BOS ISO and open a terminal (`SUPER+Return`).
2. Mount the installed root and EFI, then chroot:
```sh
mount -o subvol=@ /dev/sdXN /mnt
mount /dev/sdXP /mnt/boot/efi # the EFI partition
arch-chroot /mnt
```
3. Reinstall the bootloader (the same sequence the installer uses):
```sh
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.

BIN
assets/Icon 1024x1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
assets/Icon 256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
assets/Icon 512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Before After
Before After

View file

@ -1,11 +1,14 @@
[package]
name = "bos-settings"
version = "0.2.0"
version = "0.4.0"
edition = "2021"
[dependencies]
gtk4 = { version = "0.9", features = ["v4_12"] }
glib = "0.20"
gtk4 = { version = "0.11", features = ["v4_12"] }
glib = "0.22"
# Shared ecosystem theming — bos-settings loads the same generated stylesheet as
# breadbar/breadbox/breadpad so the whole desktop looks consistent.
bread-theme = { git = "https://github.com/Breadway/bread-ecosystem", tag = "v0.2.8", features = ["gtk"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"

View file

@ -1,87 +1,30 @@
//! Theming for bos-settings.
//!
//! bos-settings deliberately owns almost no styling: it loads the ecosystem's
//! shared stylesheet (the same one breadbar/breadbox/breadpad use, generated by
//! `bread-theme` from the pywal palette) and adds only the few layout rules
//! specific to this app's sidebar + content shell. This keeps it visually
//! identical to the rest of the bread desktop and live-recolouring for free.
use gtk4::CssProvider;
use std::cell::RefCell;
const CSS: &str = r#"
window {
background-color: #2e3440;
color: #eceff4;
// App-specific layout only — everything visual (colours, buttons, entries,
// switches, sidebar/row styling, cards, scrollbars) comes from the shared sheet.
const APP_CSS: &str = "\
.view-content { padding: 24px; }\n\
.view-content > label.title { margin-bottom: 16px; }\n\
";
thread_local! {
static APP_PROVIDER: RefCell<Option<CssProvider>> = const { RefCell::new(None) };
}
.sidebar {
background-color: #3b4252;
border-right: 1px solid #434c5e;
}
pub fn load(_display: &gtk4::gdk::Display) {
// Shared ecosystem stylesheet (loads the generated file or a rendered
// fallback, and live-reloads when the palette changes).
bread_theme::gtk::apply_shared();
.sidebar row {
padding: 8px 12px;
color: #d8dee9;
}
.sidebar row:selected {
background-color: #5e81ac;
color: #eceff4;
}
.sidebar .section-header {
padding: 12px 12px 4px 12px;
font-size: 0.75em;
font-weight: bold;
color: #616e88;
text-transform: uppercase;
letter-spacing: 1px;
}
.view-content {
padding: 24px;
}
.view-content label.title {
font-size: 1.4em;
font-weight: bold;
color: #eceff4;
margin-bottom: 16px;
}
button {
background-color: #5e81ac;
color: #eceff4;
border: none;
border-radius: 4px;
padding: 6px 16px;
}
button:hover {
background-color: #81a1c1;
}
button.destructive-action {
background-color: #bf616a;
}
button.destructive-action:hover {
background-color: #d08770;
}
entry {
background-color: #434c5e;
color: #eceff4;
border: 1px solid #4c566a;
border-radius: 4px;
}
textview {
background-color: #272c36;
color: #a3be8c;
font-family: monospace;
padding: 8px;
}
"#;
pub fn load(display: &gtk4::gdk::Display) {
let provider = CssProvider::new();
provider.load_from_string(CSS);
gtk4::style_context_add_provider_for_display(
display,
&provider,
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
// bos-settings layout, layered on top at APPLICATION priority.
APP_PROVIDER.with(|cell| bread_theme::gtk::apply_css(APP_CSS, cell));
}

View file

@ -6,7 +6,6 @@ fn css_path() -> PathBuf {
crate::config::config_dir().join("breadbar/style.css")
}
pub fn build() -> GBox {
let path = css_path();
let existing_css = std::fs::read_to_string(&path).unwrap_or_default();

View file

@ -50,9 +50,10 @@ fn stream_command(args: &[&str], log_buf: gtk4::TextBuffer) {
}
};
// Merge stderr into the channel too
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();
// Merge stderr into the channel too.
// Both are Some because we spawned with Stdio::piped() above.
let stdout = child.stdout.take().expect("stdout piped");
let stderr = child.stderr.take().expect("stderr piped");
let tx2 = sender.clone();
std::thread::spawn(move || {

View file

@ -14,7 +14,10 @@
set -euo pipefail
REPO="$(cd "$(dirname "$0")" && pwd)"
WORK=/tmp/bos-work
# WORK defaults to /tmp, but on hermes /tmp is a 16 GB tmpfs — a full xz build
# (uncompressed rootfs + squashfs + work copies) can exhaust it mid-build. Allow
# pointing it at the NVMe instead: WORK=/home/.../bos-work sudo ./build-local.sh
WORK="${WORK:-/tmp/bos-work}"
OUT="${OUT:-$REPO/out}"
# Build against a throwaway copy of the profile so the working tree stays clean
@ -22,10 +25,15 @@ OUT="${OUT:-$REPO/out}"
STAGE=/tmp/bos-iso-stage
rm -rf "$STAGE" && cp -a "$REPO/iso" "$STAGE"
# The public git.breadway.dev URL is flaky/unreachable from hermes; Forgejo is
# directly reachable over Tailscale (hestia 100.66.238.26:3002). Only rewrites
# the staged copy, never the committed pacman.conf.
# Rewrite the [breadway] pacman repo URL to the fastest reachable address.
# CI_BUILD=1 — container runs on hestia with --network=host; localhost:3002 is direct
# default — building on hermes; git.breadway.dev is flaky from there, use Tailscale
# Only ever rewrites the staged copy, never the committed pacman.conf.
if [ "${CI_BUILD:-0}" = "1" ]; then
sed -i 's#https://git.breadway.dev/api/packages/Breadway/arch/os#http://localhost:3002/api/packages/Breadway/arch/os#' "$STAGE/pacman.conf"
else
sed -i 's#https://git.breadway.dev/api/packages/Breadway/arch/os#http://100.66.238.26:3002/api/packages/Breadway/arch/os#' "$STAGE/pacman.conf"
fi
if [ "${FAST_BUILD:-0}" = "1" ]; then
echo "=== FAST_BUILD: squashfs -> zstd level 6 ==="
@ -41,7 +49,7 @@ grep airootfs_image_tool_options "$STAGE/profiledef.sh"
# created from skel (the live user and the installed user) then gets the same
# versions `bakery list` reports here, fully offline. Copied at build time so the
# binaries never bloat the git repo and always track the current bakery state.
BREAD_BINS=(bakery bread breadd breadman breadbar breadbox breadbox-sync breadcrumbs breadpad)
BREAD_BINS=(bakery bread breadd breadman breadbar breadbox breadbox-sync breadcrumbs breadpad breadpaper bread-theme)
LAPTOP_HOME="${LAPTOP_HOME:-$(getent passwd "${SUDO_USER:-$USER}" | cut -d: -f6)}"
BAKERY_BIN="$LAPTOP_HOME/.local/bin"
BAKERY_STATE="$LAPTOP_HOME/.local/state/bakery"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

View file

@ -24,23 +24,46 @@ userdel -r liveuser 2>/dev/null || true
passwd -l root || true
# ---------------------------------------------------------------------------
# Boot splash (Plymouth) — BOS logo + spinner instead of kernel text. Done
# BEFORE grub so grub.cfg picks up the new cmdline and the rebuilt initramfs.
# All best-effort: if anything here fails the system still boots (just without
# the splash) — the initramfs the initcpio module already built stays valid.
# Pacman keyring. The live medium's /etc/pacman.d/gnupg doesn't reliably carry
# over to the target (unpackfs may skip it / perms differ), leaving the installed
# system unable to verify package signatures — the first `pacman -Syu` then dies
# with "keyring is not writable / required key missing". Initialise it here so a
# fresh install can update out of the box. archlinux-keyring is already present;
# [breadway] is SigLevel=Never so it needs no key.
# ---------------------------------------------------------------------------
if command -v plymouth-set-default-theme &>/dev/null; then
# Ensure the plymouth hook is in HOOKS (plymouthcfg/initcpiocfg usually add it;
# this is the belt). Handle both the udev and systemd initramfs styles.
if ! grep -q 'plymouth' /etc/mkinitcpio.conf 2>/dev/null; then
if grep -qE '^HOOKS=.*\bsystemd\b' /etc/mkinitcpio.conf; then
sed -i 's/^\(HOOKS=.*\bsystemd\b\)/\1 sd-plymouth/' /etc/mkinitcpio.conf \
|| echo "WARN: adding sd-plymouth hook failed"
else
if command -v pacman-key &>/dev/null; then
pacman-key --init || echo "WARN: pacman-key --init failed"
pacman-key --populate archlinux || echo "WARN: pacman-key --populate failed"
fi
# ---------------------------------------------------------------------------
# Initramfs HOOKS: microcode + plymouth. Edit HOOKS first, rebuild once below.
# microcode — embeds the (autodetect-pruned) CPU microcode into the initramfs
# so it loads at early boot. The live ISO embeds ucode the same way, so the
# ISO /boot carries no separate ucode image and bos-copy-kernel stages none
# onto the target — the installed initramfs must therefore carry it itself.
# Must sit AFTER `autodetect` so it's pruned to the running CPU's microcode.
# plymouth — the BOS boot splash. Only the udev `plymouth` hook exists (there
# is NO `sd-plymouth`), so always insert it after `udev`.
# All best-effort: a failure here still leaves a bootable initramfs.
# ---------------------------------------------------------------------------
if [[ -f /etc/mkinitcpio.conf ]]; then
if ! grep -qE '^HOOKS=.*\bmicrocode\b' /etc/mkinitcpio.conf; then
sed -i 's/^\(HOOKS=.*\bautodetect\b\)/\1 microcode/' /etc/mkinitcpio.conf \
|| echo "WARN: adding microcode hook failed"
fi
if command -v plymouth-set-default-theme &>/dev/null \
&& ! grep -qE '^HOOKS=.*\bplymouth\b' /etc/mkinitcpio.conf; then
sed -i 's/^\(HOOKS=.*\budev\b\)/\1 plymouth/' /etc/mkinitcpio.conf \
|| echo "WARN: adding plymouth hook failed"
fi
fi
# ---------------------------------------------------------------------------
# Boot splash (Plymouth) — BOS logo + spinner instead of kernel text. Set the
# theme + cmdline BEFORE grub so grub.cfg picks up the new cmdline.
# ---------------------------------------------------------------------------
if command -v plymouth-set-default-theme &>/dev/null; then
# Clean boot: splash activates plymouth; hiding systemd status removes the
# "[ OK ] Started ..." text (what looked like kernel output) even if the
# splash itself doesn't grab the display (e.g. in some VMs).
@ -48,10 +71,13 @@ if command -v plymouth-set-default-theme &>/dev/null; then
sed -i 's/^\(GRUB_CMDLINE_LINUX_DEFAULT="\)/\1splash quiet vt.global_cursor_default=0 systemd.show_status=false rd.systemd.show_status=false rd.udev.log_level=3 /' \
/etc/default/grub || echo "WARN: adding splash cmdline failed"
fi
# Set the BOS theme and rebuild the initramfs (-R) with the plymouth hook.
plymouth-set-default-theme -R bos || echo "WARN: plymouth-set-default-theme failed"
plymouth-set-default-theme bos || echo "WARN: plymouth-set-default-theme failed"
fi
# Rebuild every preset (default + fallback that bos-copy-kernel wrote) so the
# microcode + plymouth HOOKS above are actually baked into the initramfs.
mkinitcpio -P || echo "WARN: mkinitcpio -P failed"
# ---------------------------------------------------------------------------
# Install GRUB (UEFI). /boot now has the kernel + initramfs, and the mount
# module has bind-mounted /proc /sys /dev /run + efivars into this chroot, so
@ -100,11 +126,33 @@ fi
# ---------------------------------------------------------------------------
for unit in NetworkManager.service bluetooth.service systemd-timesyncd.service \
tlp.service greetd.service snapper-cleanup.timer grub-btrfsd.service \
fstrim.timer cups.socket; do
fstrim.timer cups.socket avahi-daemon.service ufw.service \
fwupd-refresh.timer reflector.timer; do
systemctl enable "$unit" || echo "WARN: failed to enable $unit"
done
systemctl set-default graphical.target || echo "WARN: set-default graphical failed"
# ---------------------------------------------------------------------------
# mDNS resolution (nss-mdns): insert mdns_minimal into the hosts: line so the
# resolver answers *.local (network printers, other hosts) via avahi. Idempotent.
# ---------------------------------------------------------------------------
if [[ -f /etc/nsswitch.conf ]] && ! grep -q 'mdns_minimal' /etc/nsswitch.conf; then
sed -i 's/^\(hosts:[[:space:]]*\)/\1mdns_minimal [NOTFOUND=return] /' \
/etc/nsswitch.conf || echo "WARN: wiring nss-mdns failed"
fi
# ---------------------------------------------------------------------------
# Firewall: deny inbound by default, allow outbound, and permit inbound mDNS so
# avahi printer/service discovery keeps working. Best-effort — rule application
# happens at boot; here we only persist the policy + enable the unit.
# ---------------------------------------------------------------------------
if command -v ufw &>/dev/null; then
ufw default deny incoming || echo "WARN: ufw default deny incoming failed"
ufw default allow outgoing || echo "WARN: ufw default allow outgoing failed"
ufw allow 5353/udp || echo "WARN: ufw allow mDNS failed"
ufw --force enable || echo "WARN: ufw enable failed"
fi
# The bread ecosystem (bakery + bread, breadbar, breadbox, breadcrumbs, breadpad)
# is bakery-managed, not pacman: the binaries and bakery manifest live in
# /etc/skel/.local (baked in at ISO build time) and are copied into the user's

View file

@ -0,0 +1,7 @@
SHELL=/usr/bin/zsh
GROUP=users
HOME=/home
INACTIVE=-1
EXPIRE=
SKEL=/etc/skel
CREATE_MAIL_SPOOL=no

View file

@ -35,7 +35,8 @@ Include = /etc/pacman.d/mirrorlist
#
# Forgejo signs the repo db with a key pacman can't look up, so TrustAll
# fails. SigLevel = Never skips verification (acceptable for this private
# repo over TLS). TODO: import Forgejo's signing key + SigLevel = Required.
# repo over TLS). Future improvement: import Forgejo's signing key and
# switch to SigLevel = Required for full package verification.
# -----------------------------------------------------------------------
# The section name must match Forgejo's served db filename
# ({owner}.{group}.{domain}.db) — pacman fetches "<section>.db" from Server.

View file

@ -0,0 +1,26 @@
-- low-battery-warning — notify once when the battery runs low (zero-config).
-- Shipped active in BOS; auto-discovered by breadd. Safe on desktops too
-- (simply never fires without a battery).
local M = bread.module({ name = "low-battery-warning", version = "1.0.0" })
local warned = false
function M.on_load()
bread.on("bread.power.battery.low", function(event)
if warned then return end
warned = true
local pct = event.data.battery_percent or "?"
bread.notify("Battery low (" .. pct .. "%). Plug in soon.", {
urgency = "critical",
title = "Battery",
timeout = 10000,
})
end)
bread.on("bread.power.ac.connected", function()
warned = false
end)
end
return M

View file

@ -57,6 +57,41 @@ hl.config({
},
})
-- ---------------------------------------------------------------------------
-- Animations — snappy curves + per-leaf speeds (matches the reference laptop;
-- the hl.config default above is much slower).
-- ---------------------------------------------------------------------------
local curves = {
easeOutQuint = { type = "bezier", points = { { 0.23, 1 }, { 0.32, 1 } } },
easeInOutCubic = { type = "bezier", points = { { 0.65, 0.05 }, { 0.36, 1 } } },
almostLinear = { type = "bezier", points = { { 0.5, 0.5 }, { 0.75, 1 } } },
quick = { type = "bezier", points = { { 0.15, 0 }, { 0.1, 1 } } },
}
for name, curve in pairs(curves) do
hl.curve(name, curve)
end
local animations = {
{ leaf = "global", enabled = true, speed = 10, bezier = "default" },
{ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" },
{ leaf = "windows", enabled = true, speed = 4.79, bezier = "easeOutQuint" },
{ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" },
{ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" },
{ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" },
{ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" },
{ leaf = "workspaces", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" },
}
for _, animation in ipairs(animations) do
hl.animation(animation)
end
-- ---------------------------------------------------------------------------
-- Window rules — float + centre the onboarding popups (kitty --class …).
-- ---------------------------------------------------------------------------
hl.window_rule({ name = "bos-keybinds", match = { class = "^(bos-keybinds)$" }, float = true, size = { 760, 720 } })
hl.window_rule({ name = "bos-welcome", match = { class = "^(bos-welcome)$" }, float = true, size = { 700, 560 } })
hl.window_rule({ name = "bos-netsetup", match = { class = "^(bos-netsetup)$" }, float = true, size = { 700, 560 } })
-- ---------------------------------------------------------------------------
-- Environment (vendor-neutral; no GPU-specific vars so it works on Intel/AMD).
-- ---------------------------------------------------------------------------
@ -85,6 +120,8 @@ hl.bind(mod .. " + E", hl.dsp.exec_cmd("nautilus"))
hl.bind(mod .. " + B", hl.dsp.exec_cmd("zen-browser"))
hl.bind(mod .. " + U", hl.dsp.exec_cmd("breadpad"))
hl.bind(mod .. " + M", hl.dsp.exec_cmd("breadman"))
hl.bind(mod .. " + comma", hl.dsp.exec_cmd("bos-settings"))
hl.bind(mod .. " + slash", hl.dsp.exec_cmd("bos-keybinds"))
hl.bind(mod .. " + L", hl.dsp.exec_cmd("loginctl lock-session"))
hl.bind(mod .. " + F", hl.dsp.window.fullscreen({ action = "toggle" }))
hl.bind(mod .. " + V", hl.dsp.window.float({ action = "toggle" }))
@ -152,6 +189,9 @@ hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"),
-- ---------------------------------------------------------------------------
hl.on("hyprland.start", function()
local startup = {
-- Generate the shared bread GUI stylesheet first, so breadbar/breadbox/
-- bos-settings load it on start (they also live-reload if it changes).
"bread-theme generate",
-- Global dark theme: GTK4/libadwaita + GTK3 theme + icon + cursor.
"gsettings set org.gnome.desktop.interface color-scheme prefer-dark",
"gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark",
@ -164,10 +204,17 @@ hl.on("hyprland.start", function()
"awww-daemon",
-- set the default wallpaper once the daemon is up (retry until ready)
[[bash -c 'until awww img /usr/share/backgrounds/bos/bread-background.png 2>/dev/null; do sleep 0.3; done']],
"breadd",
-- breadd runs as a systemd user service (~/.config/systemd/user/breadd.service,
-- enabled in skel). It autostarts at login but before Hyprland exists, so
-- push the compositor's Wayland env into the user manager and restart breadd
-- to pick it up — that's how it gets HYPRLAND_INSTANCE_SIGNATURE to talk to Hyprland.
"dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE",
"systemctl --user restart breadd",
"breadbar",
"breadbox-sync",
"hypridle",
-- first-boot onboarding (self-gates after the first run)
"bos-welcome",
}
for _, cmd in ipairs(startup) do
hl.dispatch(hl.dsp.exec_cmd(cmd))

View file

@ -1,8 +1,9 @@
# BOS kitty config.
# Translucent background so Hyprland's blur shows through behind the terminal,
# while text stays fully opaque. Colours are left to kitty's default / pywal.
background_opacity 0.88
background_blur 1
# 0.6 matches the reference laptop; the actual blur is supplied by Hyprland's
# decoration:blur (kitty's own background_blur is macOS-only).
background_opacity 0.6
font_family JetBrains Mono
font_size 11.0

View file

@ -0,0 +1,51 @@
# Default applications for common file types. Without this, freshly installed
# BOS has no handler registered for images/video/text/etc., so opening a file
# from nautilus does nothing. Maps to the apps shipped in packages.x86_64.
[Default Applications]
# Images -> Loupe
image/png=org.gnome.Loupe.desktop
image/jpeg=org.gnome.Loupe.desktop
image/gif=org.gnome.Loupe.desktop
image/webp=org.gnome.Loupe.desktop
image/bmp=org.gnome.Loupe.desktop
image/tiff=org.gnome.Loupe.desktop
image/svg+xml=org.gnome.Loupe.desktop
# Audio/Video -> VLC
audio/mpeg=vlc.desktop
audio/flac=vlc.desktop
audio/ogg=vlc.desktop
audio/x-wav=vlc.desktop
audio/aac=vlc.desktop
video/mp4=vlc.desktop
video/x-matroska=vlc.desktop
video/webm=vlc.desktop
video/quicktime=vlc.desktop
video/x-msvideo=vlc.desktop
# Plain text / source -> GNOME Text Editor
text/plain=org.gnome.TextEditor.desktop
text/markdown=org.gnome.TextEditor.desktop
application/x-shellscript=org.gnome.TextEditor.desktop
application/json=org.gnome.TextEditor.desktop
application/toml=org.gnome.TextEditor.desktop
text/x-readme=org.gnome.TextEditor.desktop
# Documents / web -> Zen (PDF + HTML)
application/pdf=zen.desktop
text/html=zen.desktop
x-scheme-handler/http=zen.desktop
x-scheme-handler/https=zen.desktop
# Archives -> File Roller
application/zip=org.gnome.FileRoller.desktop
application/x-tar=org.gnome.FileRoller.desktop
application/gzip=org.gnome.FileRoller.desktop
application/x-7z-compressed=org.gnome.FileRoller.desktop
application/x-rar=org.gnome.FileRoller.desktop
application/vnd.rar=org.gnome.FileRoller.desktop
application/x-xz=org.gnome.FileRoller.desktop
application/x-bzip2=org.gnome.FileRoller.desktop
# Directories -> Nautilus
inode/directory=org.gnome.Nautilus.desktop

View file

@ -0,0 +1,21 @@
[Unit]
Description=Bread Runtime Daemon
[Service]
Type=simple
# %h = the user's home — works for any account created from this skel.
ExecStart=%h/.local/bin/breadd
Restart=on-failure
RestartSec=2
UMask=0077
RuntimeDirectory=bread
RuntimeDirectoryMode=0700
# Keep /run/user/<uid>/bread across restarts so the shared theme.css that
# bread-theme writes there (and the daemon socket) survive a `restart breadd`.
RuntimeDirectoryPreserve=yes
KillSignal=SIGTERM
TimeoutStopSec=5
Environment=RUST_LOG=info
[Install]
WantedBy=default.target

View file

@ -0,0 +1 @@
../breadd.service

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,15 @@
# BOS default zsh config — quality-of-life defaults, easy to extend.
# BOS default zsh config — Powerlevel10k prompt + plugins + pywal palette.
#
# Mirrors the BOS dev shell, but sources plugins from the distro packages
# (/usr/share/zsh/...) instead of oh-my-zsh, so there's no framework to manage.
# Customise the prompt with `p10k configure` (rewrites ~/.p10k.zsh).
# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc.
# Initialization code that may require console input (password prompts, [y/n]
# confirmations, etc.) must go above this block; everything else may go below.
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi
# History
HISTFILE=~/.zsh_history
@ -11,10 +22,21 @@ autoload -Uz compinit && compinit
zstyle ':completion:*' menu select
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
# Key bindings (emacs style + common extras)
# Emacs-style key bindings
bindkey -e
bindkey '^[[A' history-search-backward
bindkey '^[[B' history-search-forward
# Prompt — Powerlevel10k (republished to [breadway] as zsh-theme-powerlevel10k).
source /usr/share/zsh-theme-powerlevel10k/powerlevel10k.zsh-theme
# Plugins (order matters: syntax-highlighting must be sourced LAST).
ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=60'
source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh 2>/dev/null
source /usr/share/zsh/plugins/zsh-history-substring-search/zsh-history-substring-search.zsh 2>/dev/null
source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh 2>/dev/null
# history-substring-search: ↑/↓ search history by the typed prefix.
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down
# fzf — fuzzy history search on Ctrl+R, fuzzy file find on Ctrl+T
if command -v fzf &>/dev/null; then
@ -54,12 +76,18 @@ alias free='free -h'
alias grep='grep --color=auto'
alias ip='ip --color=auto'
# bakery / bread
alias update='bakery update'
# Updates — bos-update runs both channels (pacman + bakery). pacman aliased to
# sudo so `pacman -Syu` etc. just work.
alias update='bos-update'
alias pacman='sudo pacman'
# Prompt — simple and fast (no starship dep)
autoload -Uz vcs_info
precmd() { vcs_info }
zstyle ':vcs_info:git:*' formats ' (%b)'
setopt PROMPT_SUBST
PROMPT='%F{cyan}%~%f%F{yellow}${vcs_info_msg_0_}%f %(?.%F{green}.%F{red})%f '
# ~/.local/bin holds the bread* binaries baked in at build time.
export PATH="$HOME/.local/bin:$PATH"
# Powerlevel10k prompt configuration.
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
# Import pywal colour palette (drives the terminal colours from the wallpaper).
if [ -f "$HOME/.cache/wal/sequences" ]; then
cat "$HOME/.cache/wal/sequences"
fi

View file

@ -0,0 +1,6 @@
# Compressed RAM swap. systemd-zram-generator reads this and creates a zram
# device + swap at boot — no on-disk swap partition needed. Sized at half RAM
# capped to 4 GiB, zstd-compressed (typically ~3:1, so cheap headroom).
[zram0]
zram-size = min(ram / 2, 4096)
compression-algorithm = zstd

View file

@ -0,0 +1,4 @@
#!/bin/bash
# Show the BOS keybind cheatsheet in a floating terminal (bound to SUPER+/).
# The bos-keybinds window class is floated/centred by a Hyprland window rule.
exec kitty --class bos-keybinds --title "BOS Keybinds" -- less -R /usr/share/bos/keybinds.txt

View file

@ -11,7 +11,7 @@ set -e
# (breadd + breadbar + breadbox + keybinds) — proper live-media functionality,
# not an installer kiosk.
if ! id liveuser &>/dev/null; then
useradd -m -s /bin/bash liveuser
useradd -m -s /usr/bin/zsh liveuser
for g in wheel video input audio storage power; do
getent group "$g" >/dev/null 2>&1 && gpasswd -a liveuser "$g" >/dev/null || true
done

View file

@ -0,0 +1,32 @@
#!/bin/bash
# bos-update — update all of BOS in one go.
#
# BOS packages come from two channels, so a full update touches both:
# 1. pacman — Arch base/desktop + the [breadway] repo (bos-settings, etc.).
# Every transaction is snapshotted by snap-pac, so you can roll
# back from the GRUB "snapshots" submenu or BOS Settings.
# 2. bakery — the bread ecosystem apps in ~/.local/bin (bread, breadbar,
# breadbox, breadcrumbs, breadpad, breadman, bread-theme).
#
# Best-effort: a failure in one channel doesn't abort the other.
set -uo pipefail
bold() { printf '\033[1m%s\033[0m\n' "$1"; }
bold "==> System packages (pacman -Syu)"
if command -v pacman >/dev/null; then
sudo pacman -Syu || echo "WARN: pacman update failed"
else
echo "pacman not found; skipping"
fi
echo
bold "==> Bread ecosystem (bakery update --all)"
if command -v bakery >/dev/null; then
bakery update --all || echo "WARN: bakery update failed"
else
echo "bakery not found; skipping"
fi
echo
bold "==> BOS is up to date."

View file

@ -0,0 +1,34 @@
#!/bin/bash
# First-run welcome. Shows a short getting-started message once, then drops a
# marker so it never shows again. Launched from the Hyprland autostart; the
# bos-welcome window class is floated/centred by a Hyprland window rule.
set -u
# Never run in the live/installer session — only on an installed system.
[[ "$(id -un)" == "liveuser" ]] && exit 0
marker="${XDG_CONFIG_HOME:-$HOME/.config}/bos/.welcomed"
[[ -f "$marker" ]] && exit 0
mkdir -p "$(dirname "$marker")"
# First-run network check. A fresh install usually boots with no connection
# (Wi-Fi isn't configured during install), and the first `bos-update`/pacman run
# then fails with confusing DNS/"could not resolve host" errors. If
# NetworkManager reports we're not fully online, open nmtui so the user can join
# a network before anything else. Best-effort: missing nmcli/nmtui/kitty, or the
# user quitting nmtui, must never block the welcome below.
if command -v nmcli &>/dev/null; then
conn="$(nmcli networking connectivity check 2>/dev/null)"
if [[ "$conn" != "full" ]]; then
notify-send -u normal "BOS" "No internet yet — opening network setup so updates work." 2>/dev/null || true
if command -v nmtui &>/dev/null; then
kitty --class bos-netsetup --title "Connect to a network" -- nmtui connect 2>/dev/null || true
fi
fi
fi
# Mark welcomed only now, so an interrupted/aborted network step still re-prompts
# next login rather than being suppressed forever.
touch "$marker"
exec kitty --class bos-welcome --title "Welcome to BOS" -- less -R /usr/share/bos/welcome.txt

View file

@ -0,0 +1,50 @@
██████ ██████ ███████ keyboard shortcuts
██ ██ ██ ██ ██ SUPER is the Windows/Cmd key
██████ ██ ██ ███████
══════════════════════════════════════════════════════════
APPS & WINDOWS
SUPER + Return terminal (kitty)
SUPER + Space app launcher (breadbox)
SUPER + E files (nautilus)
SUPER + B browser (zen)
SUPER + U notes / reminders (breadpad)
SUPER + M package manager (breadman)
SUPER + , BOS Settings
SUPER + / this keybind cheatsheet
SUPER + L lock screen
SUPER + Backspace close window
SUPER + F fullscreen
SUPER + V toggle floating
SUPER + Shift + V clipboard history
SUPER + T toggle split direction
SUPER + Tab last window
SUPER + N exit Hyprland (log out)
SCREENSHOTS
SUPER + Shift + S select region -> file
SUPER + Shift + C select region -> clipboard
SUPER + Shift + P whole screen -> file
FOCUS & MOVE
SUPER + arrows move focus
SUPER + Shift + h/j/k/l move window
SUPER + Shift + arrows resize window
WORKSPACES
SUPER + 1..0 switch to workspace 1..10
SUPER + Shift + 1..0 move window to workspace
SUPER + [ / ] previous / next workspace
SUPER + Shift + [ / ] move window prev / next workspace
SUPER + scroll cycle workspaces
MOUSE
SUPER + left-drag move window
SUPER + right-drag resize window
MEDIA & HARDWARE KEYS
volume / brightness / play-pause / next / prev (work on lock screen)
──────────────────────────────────────────────────────────
Press q to close. Configure everything in BOS Settings (SUPER + ,).

View file

@ -0,0 +1,24 @@
Welcome to BOS — the Bread Operating System
══════════════════════════════════════════════════════════
You're running a complete Hyprland desktop with the bread
ecosystem preinstalled. A few things to get you started:
• SUPER + / show the keybind cheatsheet (any time)
• SUPER + , open BOS Settings — configure bread, the
bar, launcher, Wi-Fi profiles, notes,
snapshots and package updates, all in one
place (no config files needed)
• SUPER + Space the app launcher (breadbox)
• SUPER + Return a terminal
The bar at the top (breadbar) shows workspaces, the clock,
system stats, and your tray. Notifications appear top-right.
Your system is snapshotted on every package change — if an
update breaks something, roll back from BOS Settings or pick
a snapshot from the GRUB menu at boot.
──────────────────────────────────────────────────────────
Press q to close. This message won't show again.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

View file

@ -0,0 +1,5 @@
title Bread OS install medium (copy to RAM, UEFI)
sort-key 015
linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% copytoram=y

View file

@ -75,6 +75,11 @@ pipewire-jack
networkmanager
network-manager-applet
iw
# mDNS service/name resolution — lets CUPS auto-discover network printers and
# resolves .local hostnames (avahi-daemon enabled + nss-mdns wired in
# post-install.sh).
avahi
nss-mdns
# Wi-Fi backend for NetworkManager (its default; no extra config needed).
wpa_supplicant
bluez
@ -90,6 +95,12 @@ libpulse
# GTK3 dark theme (Adwaita-dark); without this package the gtk-theme-name in
# skel settings.ini silently falls back to the light theme for GTK3 apps.
gnome-themes-extra
# Schema + backend behind `gsettings set org.gnome.desktop.interface
# color-scheme prefer-dark` (set in hyprland.lua autostart). Without these the
# gsettings call fails silently and libadwaita apps (nautilus, gnome-text-editor)
# render in LIGHT mode regardless of the GTK theme.
gsettings-desktop-schemas
dconf
# Credential/keyring storage — browsers, SSH agents, and most apps persist
# passwords here; without it every session loses saved logins. seahorse is the
# GUI to view/manage the stored secrets and keys.
@ -107,6 +118,12 @@ noto-fonts-emoji
ttf-jetbrains-mono
# Nerd font variant — icons in terminal tools (eza --icons, fastfetch, yazi)
ttf-jetbrains-mono-nerd
# Metric-compatible (Arial/Times/Courier) so Office/web docs lay out correctly,
# broad Unicode fallback, and the Font Awesome icon glyph set (otf-, the desktop
# variant — ttf-font-awesome resolves to the web-only woff2 build).
ttf-liberation
ttf-dejavu
otf-font-awesome
# Terminal
kitty
@ -122,10 +139,13 @@ file-roller
# GUI applications a general desktop is expected to have out of the box.
# gnome-text-editor: graphical editor (terminal editors aside); gnome-calculator:
# calculator; loupe: Wayland-native image viewer (default for image files).
# calculator; loupe: Wayland-native image viewer (default for image files);
# zathura(+pdf-mupdf): lightweight Wayland PDF viewer (BOS had no PDF reader).
gnome-text-editor
gnome-calculator
loupe
zathura
zathura-pdf-mupdf
# Media player — BOS ships gstreamer codecs but otherwise has no player app.
vlc
# Web browser (served from the [Breadway] repo; AUR zen-browser-bin republished
@ -173,6 +193,12 @@ plymouth
gst-plugins-good
gst-plugins-bad
gst-plugins-ugly
# Hardware video acceleration (VA-API) — lets the AMD/Intel GPU decode H.264/HEVC/
# VP9 in mpv, VLC, and browsers instead of the CPU (cooler, longer battery on
# video). The open Mesa VA-API backend (radeonsi_drv_video.so etc.) now ships in
# the `mesa` package itself (pulled in already), so only libva (deps) + the
# `vainfo` verification tool need listing here.
libva-utils
# GUI audio mixer — useful when output device needs manual switching.
pavucontrol
@ -190,8 +216,15 @@ man-pages
less
# Base CLI tools every install should have.
# Shell
# Shell — zsh with the same prompt + plugins as the dev laptop. Powerlevel10k is
# AUR-only, so it's republished to [breadway] (see packaging/powerlevel10k). The
# three plugins come from the official repos; skel/.zshrc sources them in order
# (autosuggestions → history-substring-search → syntax-highlighting LAST).
zsh
zsh-theme-powerlevel10k
zsh-autosuggestions
zsh-history-substring-search
zsh-syntax-highlighting
# Editors
nano
micro
@ -236,6 +269,14 @@ system-config-printer
# remote post-install (needs network); the runtime is shipped ready.
flatpak
# Firewall — ufw, enabled deny-incoming in post-install.sh (mDNS allowed so
# printer discovery still works).
ufw
# Firmware updates via LVFS (works with gnome-software / fwupdmgr).
fwupd
# Compressed RAM swap — see /etc/systemd/zram-generator.conf.
zram-generator
# Icon and cursor themes
# Papirus-Dark: cohesive icon set used as the BOS default (set via gsettings in
# hyprland.lua autostart and in skel gtk-3.0/settings.ini).

View file

@ -35,7 +35,8 @@ Include = /etc/pacman.d/mirrorlist
#
# Forgejo signs the repo db with a key pacman can't look up, so TrustAll
# fails. SigLevel = Never skips verification (acceptable for this private
# repo over TLS). TODO: import Forgejo's signing key + SigLevel = Required.
# repo over TLS). Future improvement: import Forgejo's signing key and
# switch to SigLevel = Required for full package verification.
# -----------------------------------------------------------------------
# The section name must match Forgejo's served db filename
# ({owner}.{group}.{domain}.db) — pacman fetches "<section>.db" from Server.

View file

@ -21,4 +21,7 @@ file_permissions=(
["/usr/local/bin/bos-launch-calamares"]="0:0:755"
["/usr/local/bin/bos-copy-kernel"]="0:0:755"
["/usr/local/bin/bos-session"]="0:0:755"
["/usr/local/bin/bos-keybinds"]="0:0:755"
["/usr/local/bin/bos-welcome"]="0:0:755"
["/usr/local/bin/bos-update"]="0:0:755"
)

View file

@ -8,6 +8,19 @@ LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID%
# Copy-to-RAM boot option — loads airootfs.sfs entirely into RAM, so the
# installer reads from memory rather than a possibly-flaky USB (avoids SquashFS
# read errors during unpackfs). Needs enough RAM for the image (~3 GB).
LABEL archtoram
TEXT HELP
Boot Bread OS, copying the image into RAM first.
More reliable installs from USB; needs a few GB of RAM.
ENDTEXT
MENU LABEL Bread OS install medium (%ARCH%, BIOS) ^copy to RAM
LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux
INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img
APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% copytoram=y
# Accessibility boot option
LABEL archspeech
TEXT HELP

View file

@ -0,0 +1,105 @@
# BOS in-house rebuild of zsh-theme-powerlevel10k (AUR-only upstream).
# Republished to [breadway] so the ISO can pull the BOS default prompt via pacman
# (same pattern as bibata / zen-browser-bin). Upstream maintainer header kept below.
# Maintainer: Mark Wagie <mark dot wagie at proton dot me>
# Contributor: Christian Rebischke <chris.rebischke@archlinux.org>
# Contributor: Jeff Henson <jeff@henson.io>
# Contributor: Ron Asimi <ron dot asimi at gmail dot com>
# Contributor: Roman Perepelitsa <roman.perepelitsa@gmail.com>
pkgname=zsh-theme-powerlevel10k
# Whenever pkgver is updated, _libgit2ver below must also be updated.
pkgver=1.20.17 ## see P9K_VERSION in internal/p10k.zsh
_libgit2ver="tag-2ecf33948a4df9ef45a66c68b8ef24a5e60eaac6"
pkgrel=1
epoch=1
pkgdesc="Powerlevel10k is a theme for Zsh. It emphasizes speed, flexibility and out-of-the-box experience."
arch=('x86_64' 'aarch64')
url='https://github.com/romkatv/powerlevel10k'
license=('MIT')
depends=(
'glibc'
'zsh'
)
makedepends=(
'git'
'cmake'
)
optdepends=(
# It works well with Nerd Fonts, Source Code Pro, Font Awesome, Powerline,
# and even the default system fonts. The full choice of style options is
# available only when using Nerd Fonts.
'ttf-meslo-nerd-font-powerlevel10k: recommended font'
'powerline-fonts: patched fonts for powerline'
'ttf-font-nerd: full choice of style options'
)
replaces=('zsh-theme-powerlevel9k')
_commit=9253fb1c5034410c43a0c681ff8294181c54016c
# _libgit2ver depends on pkgver. They must be updated together. See libgit2_version in:
# https://raw.githubusercontent.com/romkatv/powerlevel10k/v${pkgver}/gitstatus/build.info
source=(
"git+https://github.com/romkatv/powerlevel10k.git#commit=${_commit}"
# "powerlevel10k-${pkgver}.tar.gz::https://github.com/romkatv/powerlevel10k/archive/v${pkgver}.tar.gz"
# "https://github.com/romkatv/powerlevel10k/releases/download/v$pkgver/powerlevel10k-$pkgver.tar.gz.asc"
"libgit2-${_libgit2ver}.tar.gz::https://github.com/romkatv/libgit2/archive/${_libgit2ver}.tar.gz")
sha256sums=('f0edc2cc5bfcdfcf3b94f10597c252873567a990e651d04059c887046fba6701'
'4ce11d71ee576dbbc410b9fa33a9642809cc1fa687b315f7c23eeb825b251e93')
#validpgpkeys=('8B060F8B9EB395614A669F2A90ACE942EB90C3DD') # Roman Perepelitsa <roman.perepelitsa@gmail.com>
build() {
cd "libgit2-${_libgit2ver}"
local cmake_options=(
-W no-dev
-D CMAKE_BUILD_TYPE='None'
-D ZERO_NSEC='ON'
-D THREADSAFE='ON'
-D USE_BUNDLED_ZLIB='ON'
-D REGEX_BACKEND='builtin'
-D USE_HTTP_PARSER='builtin'
-D USE_SSH='OFF'
-D USE_HTTPS='OFF'
-D BUILD_CLAR='OFF'
-D USE_GSSAPI='OFF'
-D USE_NTLMCLIENT='OFF'
-D BUILD_SHARED_LIBS='OFF'
-D ENABLE_REPRODUCIBLE_BUILDS='ON'
)
cmake "${cmake_options[@]}" .
make
# build gitstatus
cd "$srcdir/powerlevel10k/gitstatus"
export CXXFLAGS+=" -I${srcdir}/libgit2-${_libgit2ver}/include -DGITSTATUS_ZERO_NSEC -D_GNU_SOURCE"
export LDFLAGS+=" -L${srcdir}/libgit2-${_libgit2ver}"
make
}
package() {
cd powerlevel10k
find . -type f -exec install -D '{}' "$pkgdir/usr/share/${pkgname}/{}" ';'
install -d "${pkgdir}/usr/share/licenses/${pkgname}"
ln -s "/usr/share/${pkgname}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}"
# delete unnecessary files. See also: https://bugs.archlinux.org/task/66737
rm -r "${pkgdir}/usr/share/${pkgname}/.git"
rm -r "${pkgdir}/usr/share/${pkgname}/gitstatus/deps/"
rm -r "${pkgdir}/usr/share/${pkgname}/gitstatus/obj"
rm -r "${pkgdir}/usr/share/${pkgname}/gitstatus/src/"
rm -r "${pkgdir}/usr/share/${pkgname}/gitstatus/.vscode/"
rm "${pkgdir}/usr/share/${pkgname}/.gitattributes"
rm "${pkgdir}/usr/share/${pkgname}/.gitignore"
rm "${pkgdir}/usr/share/${pkgname}/Makefile"
rm "${pkgdir}/usr/share/${pkgname}/gitstatus/build"
rm "${pkgdir}/usr/share/${pkgname}/gitstatus/Makefile"
rm "${pkgdir}/usr/share/${pkgname}/gitstatus/mbuild"
rm "${pkgdir}/usr/share/${pkgname}/gitstatus/.clang-format"
rm "${pkgdir}/usr/share/${pkgname}/gitstatus/.gitignore"
rm "${pkgdir}/usr/share/${pkgname}/gitstatus/.gitattributes"
rm "${pkgdir}/usr/share/${pkgname}/gitstatus/usrbin/.gitkeep"
cd "${pkgdir}/usr/share/${pkgname}"
for file in *.zsh-theme internal/*.zsh gitstatus/*.zsh gitstatus/install; do
zsh -fc "emulate zsh -o no_aliases && zcompile -R -- $file.zwc $file"
done
}

68
scripts/smoke-test.sh Executable file
View file

@ -0,0 +1,68 @@
#!/usr/bin/env bash
# BOS post-install smoke test.
#
# Run this INSIDE a freshly installed BOS (as the main user) to assert the
# install's core invariants. It is read-only and safe to run any time.
#
# ./smoke-test.sh
#
# Exit status is non-zero if any check fails, so it can gate CI / manual QA.
set -uo pipefail
pass=0 fail=0
ok() { printf ' \033[32mPASS\033[0m %s\n' "$1"; pass=$((pass+1)); }
bad() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; fail=$((fail+1)); }
note() { printf ' \033[33m----\033[0m %s\n' "$1"; }
check() { if eval "$2" >/dev/null 2>&1; then ok "$1"; else bad "$1"; fi; }
echo "== btrfs subvolume layout =="
if command -v btrfs >/dev/null; then
# `btrfs subvolume list` needs root; try unprivileged, fall back to non-
# interactive sudo (no hang if creds aren't cached).
paths="$(btrfs subvolume list / 2>/dev/null || sudo -n btrfs subvolume list / 2>/dev/null)"
paths="$(awk '{print $NF}' <<<"$paths")"
if [ -z "$paths" ]; then
note "couldn't list subvolumes (need root) — skipping"
else
for sv in @ @home @snapshots @log @cache; do
if grep -qx "$sv" <<<"$paths"; then ok "subvolume $sv present"; else bad "subvolume $sv missing"; fi
done
fi
else
note "btrfs not installed (not a btrfs root?) — skipping subvolume checks"
fi
echo "== snapshot tooling =="
check "snapper root config exists" "[ -f /etc/snapper/configs/root ]"
check "snap-pac hook present" "pacman -Qq snap-pac"
check "grub-btrfs present" "pacman -Qq grub-btrfs"
echo "== enabled system services =="
for unit in NetworkManager.service greetd.service bluetooth.service tlp.service \
cups.socket avahi-daemon.service ufw.service systemd-timesyncd.service; do
check "$unit enabled" "systemctl is-enabled $unit"
done
check "graphical.target is default" "[ \"\$(systemctl get-default)\" = graphical.target ]"
echo "== bread ecosystem on PATH =="
for bin in bakery bread breadd breadbar breadbox breadbox-sync breadcrumbs breadpad breadman; do
check "$bin found" "command -v $bin"
done
echo "== bos-settings =="
check "bos-settings installed" "command -v bos-settings"
echo "== default dotfiles =="
check "hyprland.lua present" "[ -f \"\$HOME/.config/hypr/hyprland.lua\" ]"
check "mimeapps.list present" "[ -f \"\$HOME/.config/mimeapps.list\" ]"
check "kitty config present" "[ -f \"\$HOME/.config/kitty/kitty.conf\" ]"
echo "== bootloader (EFI) =="
check "GRUB EFI binary present" \
"[ -f /boot/efi/EFI/BOS/grubx64.efi ] || [ -f /boot/efi/EFI/BOOT/BOOTX64.EFI ]"
check "grub.cfg present" "[ -f /boot/grub/grub.cfg ]"
echo
printf 'Result: \033[32m%d passed\033[0m, \033[31m%d failed\033[0m\n' "$pass" "$fail"
[ "$fail" -eq 0 ]