diff --git a/build-local.sh b/build-local.sh index 28b9e4d..942476a 100755 --- a/build-local.sh +++ b/build-local.sh @@ -49,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 breadpaper bread-theme) +BREAD_BINS=(bakery bread breadd breadman breadbar breadbox breadbox-sync breadcrumbs breadpad breadpaper bread-theme breadmon breadsearch breadmill breadclip breadclipd breadshot) 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" @@ -69,6 +69,50 @@ install -m 0644 "$BAKERY_STATE/installed.json" "$SKEL/.local/state/bakery/instal install -m 0644 "$BAKERY_CACHE/index.json" "$SKEL/.cache/bakery/index.json" echo "baked: $(ls "$SKEL/.local/bin")" +# --- Bake systemd user services for bakery-managed bread packages ----------- +# Historically only breadd.service was hand-committed to skel; every other +# bakery package's service (breadbox-sync, breadmill, breadclipd, ...) was +# silently left out, so those daemons never start on a fresh install/live +# boot until the user re-runs `bakery install` (which needs network). +# Generalize from the same source of truth as the binary bake above: read +# the services this laptop's bakery actually installed, copy each unit file +# into skel with ExecStart rewritten from this laptop's literal home path to +# the portable `%h` specifier, and recreate whichever *.target.wants enable +# symlink bakery created locally. Units already committed by hand (breadd.service +# carries a RuntimeDirectoryPreserve=yes fix not yet upstreamed — see bread-release-build +# notes) are left alone rather than overwritten. +echo "=== baking bakery service units into skel ===" +SYSTEMD_USER_DIR="$LAPTOP_HOME/.config/systemd/user" +SKEL_SYSTEMD="$SKEL/.config/systemd/user" +mapfile -t SERVICE_UNITS < <(python3 -c " +import json +with open('$BAKERY_STATE/installed.json') as f: + d = json.load(f) +for pkg in d.get('packages', d).values(): + for s in pkg.get('services', []): + print(s) +") +for unit in "${SERVICE_UNITS[@]}"; do + if [[ -f "$SKEL_SYSTEMD/$unit" ]]; then + echo " $unit already committed in skel, leaving as-is" + continue + fi + src="$SYSTEMD_USER_DIR/$unit" + if [[ ! -f "$src" ]]; then + echo " warning: $unit not found at $src, skipping" + continue + fi + install -d -m 0755 "$SKEL_SYSTEMD" + sed "s#ExecStart=$LAPTOP_HOME/.local/bin/#ExecStart=%h/.local/bin/#" "$src" > "$SKEL_SYSTEMD/$unit" + for wants_dir in "$SYSTEMD_USER_DIR"/*.target.wants; do + [[ -L "$wants_dir/$unit" ]] || continue + target_name="$(basename "$wants_dir")" + install -d -m 0755 "$SKEL_SYSTEMD/$target_name" + ln -sf "../$unit" "$SKEL_SYSTEMD/$target_name/$unit" + done + echo " baked $unit" +done + # mkarchiso resets every airootfs file to 0644, so executables must be declared # in profiledef.sh's file_permissions array or they ship non-executable and the # exec-once launches fail with "permission denied". Inject a 0755 entry for each diff --git a/iso/airootfs/etc/calamares/modules/partition.conf b/iso/airootfs/etc/calamares/modules/partition.conf index a33f199..13e6e1e 100644 --- a/iso/airootfs/etc/calamares/modules/partition.conf +++ b/iso/airootfs/etc/calamares/modules/partition.conf @@ -5,22 +5,14 @@ efiSystemPartitionName: "EFI" defaultFileSystemType: "btrfs" -btrfsSubvolumes: - - mountPoint: / - subvolume: "@" - mountOptions: "noatime,compress=zstd,space_cache=v2" - - mountPoint: /home - subvolume: "@home" - mountOptions: "noatime,compress=zstd,space_cache=v2" - - mountPoint: /.snapshots - subvolume: "@snapshots" - mountOptions: "noatime,compress=zstd,space_cache=v2" - - mountPoint: /var/log - subvolume: "@log" - mountOptions: "noatime,compress=zstd,space_cache=v2" - - mountPoint: /var/cache - subvolume: "@cache" - mountOptions: "noatime,compress=zstd,space_cache=v2" +# NOTE: there is no `btrfsSubvolumes:` key in this Calamares version's +# partition module schema (confirmed against /usr/share/calamares/modules/ +# partition.conf and on real hardware — zero mentions of "subvolume" +# anywhere in the stock reference config). A previous version of this file +# had one; Calamares silently ignored it. Calamares' partition module only +# natively creates @ (root) and @home (home) when btrfs + separate /home is +# selected — nothing else. @snapshots/@log/@cache are created by hand in +# post-install.sh instead, after unpackfs has populated the filesystem. userSwapChoices: - none diff --git a/iso/airootfs/etc/calamares/modules/shellprocess-resolve-source.conf b/iso/airootfs/etc/calamares/modules/shellprocess-resolve-source.conf new file mode 100644 index 0000000..6f0f6f7 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/shellprocess-resolve-source.conf @@ -0,0 +1,8 @@ +--- +# Runs before unpackfs so it can hand it a source path that survives +# copytoram unmounting /run/archiso/bootmnt. See bos-resolve-airootfs. +dontChroot: true +timeout: 30 + +script: + - "/usr/bin/bash /usr/local/bin/bos-resolve-airootfs" diff --git a/iso/airootfs/etc/calamares/modules/unpackfs.conf b/iso/airootfs/etc/calamares/modules/unpackfs.conf index 7d9589e..f23db91 100644 --- a/iso/airootfs/etc/calamares/modules/unpackfs.conf +++ b/iso/airootfs/etc/calamares/modules/unpackfs.conf @@ -1,7 +1,11 @@ --- # Unpack the live squashfs onto the target partition. -# "arch" matches profiledef.sh install_dir; adjust if that changes. +# The source is a symlink written by shellprocess@resolve-source (see +# bos-resolve-airootfs) rather than a path under /run/archiso/bootmnt directly +# — that mount gets torn down by archiso's own copytoram handling, which +# self-enables on most real hardware, so a hardcoded bootmnt path would point +# at nothing on exactly the machines this install is meant to run on. unpack: - - source: "/run/archiso/bootmnt/arch/x86_64/airootfs.sfs" + - source: "/run/archiso/resolved-airootfs.sfs" sourcefs: "squashfs" destination: "" diff --git a/iso/airootfs/etc/calamares/modules/users.conf b/iso/airootfs/etc/calamares/modules/users.conf index 566fff2..887fe2c 100644 --- a/iso/airootfs/etc/calamares/modules/users.conf +++ b/iso/airootfs/etc/calamares/modules/users.conf @@ -38,4 +38,11 @@ passwordRequirements: - minlen=6 allowWeakPasswords: false -userShell: /bin/zsh + +# `userShell` (top-level) is not a real key in this Calamares version's users +# module schema (3.4.2) — it's silently ignored, and an installed user gets +# whatever the module's own default is (bash), confirmed on real hardware. +# The actual key is nested: user.shell. See /usr/share/calamares/modules/users.conf +# for the documented schema. +user: + shell: /usr/bin/zsh diff --git a/iso/airootfs/etc/calamares/post-install.sh b/iso/airootfs/etc/calamares/post-install.sh index ebfb24a..b7309db 100644 --- a/iso/airootfs/etc/calamares/post-install.sh +++ b/iso/airootfs/etc/calamares/post-install.sh @@ -79,31 +79,137 @@ fi 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 +# Install GRUB. /boot now has the kernel + initramfs, and the mount module has +# bind-mounted /proc /sys /dev /run (+ efivars on UEFI) into this chroot, so # both grub-install passes and grub-mkconfig succeed. -# 1. NVRAM entry (EFI/BOS/grubx64.efi + a firmware boot entry) -# 2. --removable copy to EFI/BOOT/BOOTX64.EFI, so firmware that ignores/loses -# the NVRAM entry (the "no boot device / PXE fallback" failure) still finds -# a bootloader. +# +# BOS ships a syslinux BIOS boot mode on the ISO (profiledef.sh bootmodes +# includes bios.syslinux), but this only ever ran the UEFI grub-install path — +# a BIOS install would complete successfully and then have no bootloader at +# all. Branch on /sys/firmware/efi (present only when booted UEFI) and install +# the matching GRUB target; on BIOS, grub-install needs the whole disk, not a +# partition, so it's derived from the mounted root via lsblk. +# UEFI: 1. NVRAM entry (EFI/BOS/grubx64.efi + a firmware boot entry) +# 2. --removable copy to EFI/BOOT/BOOTX64.EFI, so firmware that +# ignores/loses the NVRAM entry still finds a bootloader. +# BIOS: MBR install onto the disk hosting /. # --------------------------------------------------------------------------- if command -v grub-install &>/dev/null; then - grub-install --target=x86_64-efi --efi-directory=/boot/efi \ - --bootloader-id=BOS --recheck \ - || echo "WARN: grub-install (nvram) failed" - grub-install --target=x86_64-efi --efi-directory=/boot/efi \ - --removable --recheck \ - || echo "WARN: grub-install (removable) failed" + if [[ -d /sys/firmware/efi ]]; then + grub-install --target=x86_64-efi --efi-directory=/boot/efi \ + --bootloader-id=BOS --recheck \ + || echo "WARN: grub-install (nvram) failed" + grub-install --target=x86_64-efi --efi-directory=/boot/efi \ + --removable --recheck \ + || echo "WARN: grub-install (removable) failed" + else + ROOT_DEV="$(findmnt -no SOURCE / | sed 's/\[.*\]//')" + ROOT_DISK="$(lsblk -no pkname "$ROOT_DEV" 2>/dev/null)" + if [[ -n "$ROOT_DISK" ]]; then + grub-install --target=i386-pc --recheck "/dev/$ROOT_DISK" \ + || echo "WARN: grub-install (BIOS) failed" + else + echo "WARN: could not determine the disk hosting / (root device: ${ROOT_DEV:-unknown}) — BIOS grub-install skipped" + fi + fi fi if command -v grub-mkconfig &>/dev/null; then grub-mkconfig -o /boot/grub/grub.cfg || echo "WARN: grub-mkconfig failed" fi +# --------------------------------------------------------------------------- +# Create @snapshots, @log, @cache as top-level btrfs subvolumes (peers of @, +# not nested under it — so snapshots of @ don't recursively include +# themselves, and log/cache churn doesn't bloat @'s snapshot history). +# +# iso/partition.conf's `btrfsSubvolumes:` key does NOT do this — verified on +# real hardware that it's not a recognized key in this Calamares version's +# partition module schema at all (same class of bug as the `userShell` fix +# below: Calamares silently ignores unknown top-level keys). Calamares' own +# btrfs support only natively creates @ and @home; everything else has to be +# done by hand, here, after unpackfs has populated / but before anything +# reads/writes /var/log or /var/cache going forward. Existing content in +# those two dirs (real files already unpacked from the squashfs) is migrated +# into the new subvolumes before mounting over them, so nothing is lost — +# just shadowed by the mount, same as any other mountpoint. +# --------------------------------------------------------------------------- +if command -v btrfs &>/dev/null; then + ROOT_DEV="$(findmnt -no SOURCE / | sed 's/\[.*\]//')" + ROOT_UUID="$(blkid -s UUID -o value "$ROOT_DEV" 2>/dev/null)" + BTRFS_TOP=/.btrfs-top-tmp + + if [[ -z "$ROOT_UUID" ]]; then + echo "WARN: could not determine root filesystem UUID — skipping @snapshots/@log/@cache creation" + else + mkdir -p "$BTRFS_TOP" + if mount -o subvolid=5 "$ROOT_DEV" "$BTRFS_TOP"; then + for sv in @snapshots @log @cache; do + if ! btrfs subvolume show "$BTRFS_TOP/$sv" &>/dev/null; then + btrfs subvolume create "$BTRFS_TOP/$sv" || echo "WARN: creating $sv failed" + fi + done + rsync -aAX /var/log/ "$BTRFS_TOP/@log/" || echo "WARN: migrating /var/log into @log failed" + rsync -aAX /var/cache/ "$BTRFS_TOP/@cache/" || echo "WARN: migrating /var/cache into @cache failed" + umount "$BTRFS_TOP" + rmdir "$BTRFS_TOP" + + OPTS="noatime,compress=zstd,space_cache=v2" + grep -q "@snapshots" /etc/fstab || echo "UUID=$ROOT_UUID /.snapshots btrfs subvol=/@snapshots,$OPTS 0 0" >> /etc/fstab + grep -q "@log" /etc/fstab || echo "UUID=$ROOT_UUID /var/log btrfs subvol=/@log,$OPTS 0 0" >> /etc/fstab + grep -q "@cache" /etc/fstab || echo "UUID=$ROOT_UUID /var/cache btrfs subvol=/@cache,$OPTS 0 0" >> /etc/fstab + + mkdir -p /.snapshots + mount /.snapshots || echo "WARN: mounting /.snapshots failed" + mount /var/log || echo "WARN: mounting /var/log failed" + mount /var/cache || echo "WARN: mounting /var/cache failed" + else + echo "WARN: could not mount btrfs top-level — skipping @snapshots/@log/@cache creation" + fi + fi +fi + # --------------------------------------------------------------------------- # Snapper root config (root is btrfs). +# +# @snapshots is now mounted at /.snapshots (created above) — a dedicated +# top-level subvolume, a peer of @ rather than nested under it, so snapshots +# aren't themselves recursively snapshotted. `snapper create-config` insists +# on creating that subvolume itself and refuses whenever /.snapshots already +# exists, mounted or not — silently, so without this dance every downstream +# sed below is a no-op and BOS ships with its advertised auto-snapshot/ +# rollback feature entirely non-functional. +# Unmount the real subvolume, let snapper create + own its nested one, then +# discard that and remount the real @snapshots in its place (fstab entry was +# added above, right after the subvolume was created). +# +# Confirmed on real hardware: a plain `umount /.snapshots` here can +# transiently fail inside the Calamares chroot (the same mount unmounts +# cleanly moments later once booted normally — a chroot-specific busy-mount +# race, not a logic error), which cascades into snapper create-config +# refusing because .snapshots "already exists". Retry a few times, then +# fall back to a lazy unmount (detaches the mountpoint immediately even if +# something still transiently references it) rather than give up. # --------------------------------------------------------------------------- if command -v snapper &>/dev/null; then + unmounted=0 + for _ in 1 2 3 4 5; do + if umount /.snapshots 2>/dev/null; then + unmounted=1 + break + fi + sleep 1 + done + if [[ "$unmounted" != "1" ]]; then + echo "WARN: umount /.snapshots failed after retries, forcing lazy unmount" + umount -l /.snapshots || echo "WARN: lazy umount /.snapshots also failed" + fi + rmdir /.snapshots || echo "WARN: rmdir /.snapshots failed" snapper -c root create-config / || echo "WARN: snapper create-config failed" + if [[ -d /.snapshots ]]; then + btrfs subvolume delete /.snapshots || echo "WARN: deleting snapper's own .snapshots subvolume failed" + fi + mkdir -p /.snapshots + mount /.snapshots || echo "WARN: remounting the real @snapshots subvolume failed" if [[ -f /etc/snapper/configs/root ]]; then sed -i 's/TIMELINE_CREATE="yes"/TIMELINE_CREATE="no"/' /etc/snapper/configs/root sed -i 's/NUMBER_CLEANUP="no"/NUMBER_CLEANUP="yes"/' /etc/snapper/configs/root diff --git a/iso/airootfs/etc/calamares/settings.conf b/iso/airootfs/etc/calamares/settings.conf index f97ae87..8846a56 100644 --- a/iso/airootfs/etc/calamares/settings.conf +++ b/iso/airootfs/etc/calamares/settings.conf @@ -3,10 +3,16 @@ modules-search: [/etc/calamares/modules, /usr/lib/calamares/modules] # Second shellprocess instance: copies the live kernel into the target /boot # (archiso keeps it out of the squashfs) before the bootloader step runs. +# Third: resolves the live squashfs's real location before unpackfs runs — +# copytoram unmounts /run/archiso/bootmnt, so a hardcoded path there can point +# at nothing depending on how the medium was booted. instances: - id: kernel module: shellprocess config: shellprocess-kernel.conf +- id: resolve-source + module: shellprocess + config: shellprocess-resolve-source.conf sequence: - show: @@ -19,6 +25,7 @@ sequence: - exec: - partition - mount + - shellprocess@resolve-source - unpackfs - machineid - fstab diff --git a/iso/airootfs/etc/mkinitcpio.conf.d/archiso.conf b/iso/airootfs/etc/mkinitcpio.conf.d/archiso.conf index 5c008e5..e53d079 100644 --- a/iso/airootfs/etc/mkinitcpio.conf.d/archiso.conf +++ b/iso/airootfs/etc/mkinitcpio.conf.d/archiso.conf @@ -1,3 +1,3 @@ -HOOKS=(base udev microcode modconf kms memdisk archiso archiso_loop_mnt archiso_pxe_common archiso_pxe_nbd archiso_pxe_http archiso_pxe_nfs block filesystems keyboard) +HOOKS=(base udev plymouth microcode modconf kms memdisk archiso archiso_loop_mnt archiso_pxe_common archiso_pxe_nbd archiso_pxe_http archiso_pxe_nfs block filesystems keyboard) COMPRESSION="xz" COMPRESSION_OPTIONS=(-9e) diff --git a/iso/airootfs/etc/os-release b/iso/airootfs/etc/os-release new file mode 100644 index 0000000..e8cffa1 --- /dev/null +++ b/iso/airootfs/etc/os-release @@ -0,0 +1,12 @@ +NAME="BOS" +PRETTY_NAME="Bread OS" +ID=bos +ID_LIKE=arch +BUILD_ID=rolling +ANSI_COLOR="38;2;23;147;209" +HOME_URL="https://breadway.dev" +DOCUMENTATION_URL="https://wiki.archlinux.org/" +SUPPORT_URL="https://bbs.archlinux.org/" +BUG_REPORT_URL="https://git.breadway.dev/Breadway/bos/issues" +PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/" +LOGO=archlinux-logo diff --git a/iso/airootfs/etc/plymouth/plymouthd.conf b/iso/airootfs/etc/plymouth/plymouthd.conf new file mode 100644 index 0000000..dc44279 --- /dev/null +++ b/iso/airootfs/etc/plymouth/plymouthd.conf @@ -0,0 +1,2 @@ +[Daemon] +Theme=bos diff --git a/iso/airootfs/etc/skel/.config/breadbox/config.toml b/iso/airootfs/etc/skel/.config/breadbox/config.toml index 797b3cc..0b47001 100644 --- a/iso/airootfs/etc/skel/.config/breadbox/config.toml +++ b/iso/airootfs/etc/skel/.config/breadbox/config.toml @@ -1,3 +1,3 @@ [[context]] name = "default" -apps = ["firefox", "foot", "nautilus", "code"] +priority = ["Zen Browser", "kitty", "Files"] diff --git a/iso/airootfs/etc/skel/.config/fastfetch/bread.txt b/iso/airootfs/etc/skel/.config/fastfetch/bread.txt new file mode 100644 index 0000000..a339adb --- /dev/null +++ b/iso/airootfs/etc/skel/.config/fastfetch/bread.txt @@ -0,0 +1,20 @@ +               +      ....',,;;;;,,'....       +   ..'cdkKXWMMMWNNNNNWMMMNKOdc'..     +  ..;xNW0xoc;,'...........',:oxKWNOc..   + .'XWx;..........................,dWN,.  + .xMo..............................cMk.  + .;WK'............................'OM:.  +   'oW0.....d:...................0Md'    +   .kM:...lNMKc................,MO.    +  .dMc.....lXMXl..............:Mk.   +  .xMc.......kMMX'............;Mk.   +  .kM:.....:KMNo..............,M0.   +  .KM,...cXMXl....lllllllll....MX.   + ..NM....,kc......0000OOOOO'...MW..  +  ..MW..........................NM..   +  ..MX..........................KM,.   +  .'MX..........................XM;.   +   .XMKOkxxddddoooooodddddxxkkO0MN.    +      ,:ccclllllooolllllccc::;.      +                         diff --git a/iso/airootfs/etc/skel/.config/fastfetch/config.jsonc b/iso/airootfs/etc/skel/.config/fastfetch/config.jsonc new file mode 100644 index 0000000..d5b8edb --- /dev/null +++ b/iso/airootfs/etc/skel/.config/fastfetch/config.jsonc @@ -0,0 +1,38 @@ +{ + "$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json", + "logo": { + "type": "file", + "source": "~/.config/fastfetch/bread.txt", + "width": 30, + "padding": { + "right": 5 + } + }, + "display": { + "separator": " ", + "color": { + "keys": "cyan", + "title": "bright_blue" + } + }, + "modules": [ + "title", + "separator", + "os", + "host", + "kernel", + "uptime", + "packages", + "shell", + "display", + "wm", + "terminal", + "cpu", + "gpu", + "memory", + "swap", + "disk", + "battery", + "colors" + ] +} diff --git a/iso/airootfs/etc/skel/.config/hypr/hyprland.lua b/iso/airootfs/etc/skel/.config/hypr/hyprland.lua index d9a6eb8..e9b785d 100644 --- a/iso/airootfs/etc/skel/.config/hypr/hyprland.lua +++ b/iso/airootfs/etc/skel/.config/hypr/hyprland.lua @@ -125,7 +125,10 @@ 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" })) -hl.bind(mod .. " + SHIFT + V", hl.dsp.exec_cmd([[bash -c 'cliphist list | fzf --reverse --prompt="Clipboard > " | cliphist decode | wl-copy']])) +-- breadclip (its own gtk4-layer-shell popup — not a TUI, so no terminal +-- needed). Previously piped cliphist through fzf directly from the +-- compositor with no terminal attached, which was a silent no-op. +hl.bind(mod .. " + SHIFT + V", hl.dsp.exec_cmd("breadclip")) hl.bind(mod .. " + T", hl.dsp.layout("togglesplit")) hl.bind(mod .. " + Tab", hl.dsp.focus({ urgent_or_last = true })) hl.bind(mod .. " + N", hl.dsp.exit()) @@ -198,8 +201,9 @@ hl.on("hyprland.start", function() "gsettings set org.gnome.desktop.interface icon-theme Papirus-Dark", "gsettings set org.gnome.desktop.interface cursor-theme Bibata-Modern-Ice", "gsettings set org.gnome.desktop.interface cursor-size 24", - -- Clipboard history daemon (feeds SUPER+V history picker via wl-paste). - "wl-paste --type text --watch cliphist store", + -- Clipboard history is breadclipd, a bakery-managed systemd --user + -- service (auto-started via skel — see build-local.sh's service bake) + -- rather than an exec-once here. "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1", "awww-daemon", -- set the default wallpaper once the daemon is up (retry until ready) @@ -210,8 +214,20 @@ hl.on("hyprland.start", function() -- 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", + -- graphical-session.target ships with RefuseManualStart=yes (systemd + -- convention — only a session manager like uwsm is meant to activate + -- it), confirmed on real hardware: `systemctl --user start + -- graphical-session.target` fails outright ("Operation refused"). + -- BOS doesn't use uwsm, and nothing else activates that target, so + -- breadclipd (WantedBy=graphical-session.target) never started. + -- Start it directly instead. If more graphical-session.target + -- services show up later, add them here too. + "systemctl --user start breadclipd.service", "breadbar", - "breadbox-sync", + -- breadbox-sync is a Type=oneshot systemd --user service + -- (WantedBy=default.target, no Hyprland IPC dependency) — it + -- already runs on login via the unit baked into skel; exec'ing it + -- again here would just start it twice. "hypridle", -- first-boot onboarding (self-gates after the first run) "bos-welcome", diff --git a/iso/airootfs/usr/local/bin/bos-copy-kernel b/iso/airootfs/usr/local/bin/bos-copy-kernel index ee23c30..7f7ad95 100755 --- a/iso/airootfs/usr/local/bin/bos-copy-kernel +++ b/iso/airootfs/usr/local/bin/bos-copy-kernel @@ -7,19 +7,40 @@ # (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. +# Runs in the LIVE environment (Calamares shellprocess, dontChroot); the +# target root mount point is passed as $1. +# +# The kernel is read from /usr/lib/modules/$(uname -r)/vmlinuz — part of the +# live squashfs itself (the linux package always installs it there), not from +# the ISO's separate arch/boot/x86_64/ dir under /run/archiso/bootmnt. That +# mount gets torn down by archiso's own copytoram handling, which self-enables +# on most real hardware, so depending on it here would leave $ROOT/boot empty +# and the install unbootable on exactly the machines this is meant to run on. set -uo pipefail ROOT="${1:?target root required}" -SRC="/run/archiso/bootmnt/arch/boot/x86_64" +KVER="$(uname -r)" +SRC_KERNEL="/usr/lib/modules/${KVER}/vmlinuz" +SRC_BOOT="/run/archiso/bootmnt/arch/boot/x86_64" install -d -m 0755 "$ROOT/boot" -cp -f "$SRC/vmlinuz-linux" "$ROOT/boot/vmlinuz-linux" -# Microcode, if the live medium carries it (grub-mkconfig picks it up). +if [ -f "$SRC_KERNEL" ]; then + cp -f "$SRC_KERNEL" "$ROOT/boot/vmlinuz-linux" +elif [ -f "$SRC_BOOT/vmlinuz-linux" ]; then + echo "WARN: $SRC_KERNEL missing, falling back to $SRC_BOOT" + cp -f "$SRC_BOOT/vmlinuz-linux" "$ROOT/boot/vmlinuz-linux" +else + echo "ERROR: no kernel image found (checked $SRC_KERNEL and $SRC_BOOT/vmlinuz-linux) — install would be unbootable" >&2 + exit 1 +fi + +# Microcode, if the live medium carries it (grub-mkconfig picks it up). This +# is best-effort: post-install.sh's mkinitcpio `microcode` HOOKS entry embeds +# microcode from /usr/lib/firmware directly, so a missing standalone ucode.img +# here is not boot-critical, unlike the kernel image above. for u in amd-ucode.img intel-ucode.img; do - [ -f "$SRC/$u" ] && cp -f "$SRC/$u" "$ROOT/boot/$u" + [ -f "$SRC_BOOT/$u" ] && cp -f "$SRC_BOOT/$u" "$ROOT/boot/$u" done # Replace the archiso initramfs setup that unpackfs copied from the live medium. diff --git a/iso/airootfs/usr/local/bin/bos-live-setup b/iso/airootfs/usr/local/bin/bos-live-setup index 32a83f6..ad54075 100644 --- a/iso/airootfs/usr/local/bin/bos-live-setup +++ b/iso/airootfs/usr/local/bin/bos-live-setup @@ -34,15 +34,18 @@ fi # Start Hyprland on tty1 login; capture output and fall back to a shell so a # failed compositor start is visible rather than a blank looping cursor. -cat >/home/liveuser/.bash_profile <<'EOF' +# liveuser's shell is zsh (see useradd above), which never sources +# .bash_profile — this must be .zprofile (zsh's login-shell hook) or it never +# runs at all and the live session boots to a bare console. +cat >/home/liveuser/.zprofile <<'EOF' if [[ "$(tty)" == /dev/tty1 ]] && [[ -z "$WAYLAND_DISPLAY" ]]; then export WLR_RENDERER_ALLOW_SOFTWARE=1 export WLR_NO_HARDWARE_CURSORS=1 # Log to a user-writable path (/var/log is root-only; redirecting there # would fail and silently keep the compositor from ever launching). - Hyprland &>/tmp/hyprland-live.log + start-hyprland &>/tmp/hyprland-live.log echo "Hyprland exited (rc=$?). Log: /tmp/hyprland-live.log" - exec bash -i + exec zsh -i fi EOF diff --git a/iso/airootfs/usr/local/bin/bos-resolve-airootfs b/iso/airootfs/usr/local/bin/bos-resolve-airootfs new file mode 100644 index 0000000..3b347f6 --- /dev/null +++ b/iso/airootfs/usr/local/bin/bos-resolve-airootfs @@ -0,0 +1,29 @@ +#!/bin/bash +# Resolve the real location of the live squashfs before Calamares' unpackfs +# module runs, and leave a stable symlink for it to unpack from. +# +# archiso's own initcpio hook unmounts /run/archiso/bootmnt once copytoram has +# copied the image to a tmpfs (/run/archiso/copytoram/airootfs.sfs) — and +# copytoram=auto self-enables on most real hardware (non-optical boot device, +# image under 4 GiB, enough free RAM), not just when explicitly requested. A +# Calamares unpackfs.conf source path hardcoded to /run/archiso/bootmnt/... +# would then point at nothing. Check both locations here, in a real script, +# and hand unpackfs.conf one fixed path that always resolves. +set -uo pipefail + +DEST="/run/archiso/resolved-airootfs.sfs" +CANDIDATES=( + "/run/archiso/copytoram/airootfs.sfs" + "/run/archiso/bootmnt/arch/x86_64/airootfs.sfs" +) + +for c in "${CANDIDATES[@]}"; do + if [ -f "$c" ]; then + ln -sf "$c" "$DEST" + echo "resolved live squashfs: $c -> $DEST" + exit 0 + fi +done + +echo "ERROR: could not find the live squashfs in any known location (checked: ${CANDIDATES[*]})" >&2 +exit 1 diff --git a/iso/airootfs/usr/local/bin/bos-session b/iso/airootfs/usr/local/bin/bos-session index 8fecd2c..d7655fc 100644 --- a/iso/airootfs/usr/local/bin/bos-session +++ b/iso/airootfs/usr/local/bin/bos-session @@ -7,9 +7,14 @@ # 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. +# +# Launched via start-hyprland (ships with the hyprland package) rather than the +# raw Hyprland binary — Hyprland upstream no longer recommends exec'ing it +# directly; start-hyprland wraps it in a watchdog process that also gives us +# crash recovery for free. source /etc/profile 2>/dev/null export XDG_SESSION_TYPE=wayland export XDG_CURRENT_DESKTOP=Hyprland -exec Hyprland +exec start-hyprland diff --git a/iso/airootfs/usr/local/bin/bos-update b/iso/airootfs/usr/local/bin/bos-update index 42231ea..53ac3c1 100644 --- a/iso/airootfs/usr/local/bin/bos-update +++ b/iso/airootfs/usr/local/bin/bos-update @@ -5,8 +5,10 @@ # 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). +# 2. bakery — the bread ecosystem apps in ~/.local/bin (whatever `bakery list` +# reports as installed — bread, breadbar, breadbox, breadcrumbs, +# breadpad, breadman, bread-theme, breadpaper, breadmon, +# breadsearch, breadclip, breadshot, ...). # # Best-effort: a failure in one channel doesn't abort the other. set -uo pipefail diff --git a/iso/efiboot/loader/entries/01-archiso-linux-copytoram.conf b/iso/efiboot/loader/entries/01-archiso-linux-copytoram.conf index 289bd88..96da700 100644 --- a/iso/efiboot/loader/entries/01-archiso-linux-copytoram.conf +++ b/iso/efiboot/loader/entries/01-archiso-linux-copytoram.conf @@ -2,4 +2,4 @@ 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 +options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% copytoram=y quiet splash vt.global_cursor_default=0 systemd.show_status=false rd.systemd.show_status=false rd.udev.log_level=3 diff --git a/iso/efiboot/loader/entries/01-archiso-linux.conf b/iso/efiboot/loader/entries/01-archiso-linux.conf index d872e48..c26c61e 100644 --- a/iso/efiboot/loader/entries/01-archiso-linux.conf +++ b/iso/efiboot/loader/entries/01-archiso-linux.conf @@ -2,4 +2,4 @@ title Bread OS install medium (%ARCH%, UEFI) sort-key 01 linux /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux initrd /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img -options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% +options archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% quiet splash vt.global_cursor_default=0 systemd.show_status=false rd.systemd.show_status=false rd.udev.log_level=3 diff --git a/iso/packages.x86_64 b/iso/packages.x86_64 index ab0d1e4..41902e1 100644 --- a/iso/packages.x86_64 +++ b/iso/packages.x86_64 @@ -178,10 +178,10 @@ brightnessctl grim slurp # Clipboard (Wayland copy/paste; also clipboard screenshots) and media keys. +# Clipboard history is breadclip/breadclipd (bakery-managed, SUPER+SHIFT+V), +# not cliphist — wl-clipboard is still needed directly by breadclip/breadshot. wl-clipboard playerctl -# Clipboard history daemon (stores wl-clipboard events; breadbox bind replays them). -cliphist # Wallpaper daemon + pywal (drives the bread* colour palette from the wallpaper). awww python-pywal diff --git a/iso/profiledef.sh b/iso/profiledef.sh index 709071a..fc4be34 100644 --- a/iso/profiledef.sh +++ b/iso/profiledef.sh @@ -20,6 +20,7 @@ file_permissions=( ["/usr/local/bin/bos-live-setup"]="0:0:755" ["/usr/local/bin/bos-launch-calamares"]="0:0:755" ["/usr/local/bin/bos-copy-kernel"]="0:0:755" + ["/usr/local/bin/bos-resolve-airootfs"]="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" diff --git a/iso/syslinux/archiso_sys-linux.cfg b/iso/syslinux/archiso_sys-linux.cfg index 0ec10f1..febc966 100644 --- a/iso/syslinux/archiso_sys-linux.cfg +++ b/iso/syslinux/archiso_sys-linux.cfg @@ -6,7 +6,7 @@ ENDTEXT MENU LABEL Bread OS install medium (%ARCH%, BIOS) LINUX /%INSTALL_DIR%/boot/%ARCH%/vmlinuz-linux INITRD /%INSTALL_DIR%/boot/%ARCH%/initramfs-linux.img -APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% +APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% quiet splash vt.global_cursor_default=0 systemd.show_status=false rd.systemd.show_status=false rd.udev.log_level=3 # 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 @@ -19,7 +19,7 @@ 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 +APPEND archisobasedir=%INSTALL_DIR% archisosearchuuid=%ARCHISO_UUID% copytoram=y quiet splash vt.global_cursor_default=0 systemd.show_status=false rd.systemd.show_status=false rd.udev.log_level=3 # Accessibility boot option LABEL archspeech