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).
This commit is contained in:
Breadway 2026-06-16 09:09:34 +08:00
parent 787cc0e4c5
commit f8ae8fe125
35 changed files with 787 additions and 180 deletions

View file

@ -3,8 +3,9 @@
#
# archiso keeps vmlinuz/initramfs in the ISO boot dir (arch/boot/x86_64/), NOT
# in the squashfs, so the rootfs that unpackfs lays down has an empty /boot.
# Without a kernel, the chroot's `mkinitcpio -P` and `grub-mkconfig` produce
# nothing and the installed system is unbootable.
# The kernel must be present before Calamares' `initcpio` module runs mkinitcpio
# (the stock linux.preset points ALL_kver at /boot/vmlinuz-linux) and before the
# `bootloader` module runs grub — otherwise the installed system is unbootable.
#
# Runs in the LIVE environment (Calamares shellprocess, dontChroot) so it can
# read /run/archiso/bootmnt; the target root mount point is passed as $1.
@ -21,4 +22,25 @@ for u in amd-ucode.img intel-ucode.img; do
[ -f "$SRC/$u" ] && cp -f "$SRC/$u" "$ROOT/boot/$u"
done
echo "Copied live kernel into $ROOT/boot"
# Replace the archiso initramfs setup that unpackfs copied from the live medium.
# On archiso the linux preset is PRESETS=('archiso') using archiso.conf (the live
# HOOKS). Calamares' `initcpio` runs `mkinitcpio -P`, which would build that
# archiso preset and either bake the live-boot hooks into the install or fail
# once archiso.conf is gone. Drop the drop-in and write a stock default+fallback
# preset so `initcpio` produces a normal, bootable initramfs from the config that
# the `initcpiocfg` module generates at /etc/mkinitcpio.conf.
rm -f "$ROOT/etc/mkinitcpio.conf.d/archiso.conf"
install -d -m 0755 "$ROOT/etc/mkinitcpio.d"
cat >"$ROOT/etc/mkinitcpio.d/linux.preset" <<'PRESET'
# mkinitcpio preset file for the 'linux' package
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux"
PRESETS=('default' 'fallback')
default_image="/boot/initramfs-linux.img"
fallback_image="/boot/initramfs-linux-fallback.img"
fallback_options="-S autodetect"
PRESET
echo "Copied live kernel into $ROOT/boot; reset mkinitcpio to a stock preset"

View file

@ -19,16 +19,16 @@ if ! id liveuser &>/dev/null; then
fi
# Layer the installer onto the live desktop: auto-launch it, and bind Super+I to
# relaunch it after it's been closed. Appended to (not replacing) the skel
# Hyprland config so the full desktop stays intact.
HYPR=/home/liveuser/.config/hypr/hyprland.conf
# relaunch it after it's been closed. Appended (in Lua) to the skel hyprland.lua
# native config so the full desktop stays intact.
HYPR=/home/liveuser/.config/hypr/hyprland.lua
install -d -m 0755 -o liveuser -g liveuser /home/liveuser/.config/hypr
if ! grep -q bos-launch-calamares "$HYPR" 2>/dev/null; then
cat >>"$HYPR" <<'EOF'
# --- live-media installer (added by bos-live-setup; absent on installed system) ---
exec-once = bos-launch-calamares
bind = SUPER, I, exec, bos-launch-calamares
-- --- live-media installer (added by bos-live-setup; absent on installed system) ---
hl.bind("SUPER + I", hl.dsp.exec_cmd("bos-launch-calamares"))
hl.on("hyprland.start", function() hl.dispatch(hl.dsp.exec_cmd("bos-launch-calamares")) end)
EOF
fi

View file

@ -0,0 +1,15 @@
#!/bin/bash
# BOS graphical session launcher, run by greetd on the INSTALLED system after
# the user authenticates (see /etc/greetd/config.toml).
#
# greetd does not start a login shell, so /etc/profile.d is never sourced — which
# means ~/.local/bin (where bakery installs the bread ecosystem: breadd, breadbar,
# breadbox-sync, …) would be missing from PATH and the Hyprland `exec-once`
# launches would fail. Source the login profile here so PATH is correct, set the
# Wayland session hints, then hand off to Hyprland.
source /etc/profile 2>/dev/null
export XDG_SESSION_TYPE=wayland
export XDG_CURRENT_DESKTOP=Hyprland
exec Hyprland

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

View file

@ -0,0 +1,8 @@
[Plymouth Theme]
Name=BOS
Description=Bread Operating System boot splash — logo, spinner, status
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/bos
ScriptFile=/usr/share/plymouth/themes/bos/bos.script

View file

@ -0,0 +1,49 @@
# BOS Plymouth boot splash (script module).
# Bread-brown background, centred white BOS logo, a spinning accent ring, and a
# status line at the bottom. Colours match the bread palette.
# --- background (#230b00) ---
Window.SetBackgroundTopColor(0.137, 0.043, 0.0);
Window.SetBackgroundBottomColor(0.137, 0.043, 0.0);
screen_w = Window.GetWidth();
screen_h = Window.GetHeight();
# --- logo (centred, slightly above the middle) ---
logo.image = Image("logo.png");
logo.sprite = Sprite(logo.image);
logo.x = screen_w / 2 - logo.image.GetWidth() / 2;
logo.y = screen_h / 2 - logo.image.GetHeight() / 2 - 40;
logo.sprite.SetX(logo.x);
logo.sprite.SetY(logo.y);
logo.sprite.SetZ(1);
# --- spinner (rotating accent ring, below the logo) ---
spinner.image = Image("spinner.png");
spinner.sprite = Sprite();
spinner.cx = screen_w / 2;
spinner.cy = logo.y + logo.image.GetHeight() + 60;
spinner.angle = 0;
fun refresh_callback() {
spinner.angle += 0.10;
if (spinner.angle > 6.28318) spinner.angle -= 6.28318;
rotated = spinner.image.Rotate(spinner.angle);
spinner.sprite.SetImage(rotated);
spinner.sprite.SetX(spinner.cx - rotated.GetWidth() / 2);
spinner.sprite.SetY(spinner.cy - rotated.GetHeight() / 2);
spinner.sprite.SetZ(2);
}
Plymouth.SetRefreshFunction(refresh_callback);
# --- status line (cream text, near the bottom) ---
status.sprite = Sprite();
fun show_status(text) {
status.image = Image.Text(text, 0.945, 0.863, 0.741);
status.sprite.SetImage(status.image);
status.sprite.SetX(screen_w / 2 - status.image.GetWidth() / 2);
status.sprite.SetY(screen_h * 0.84);
status.sprite.SetZ(2);
}
Plymouth.SetMessageFunction(show_status);
Plymouth.SetUpdateStatusFunction(show_status);

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB