diff --git a/.gitignore b/.gitignore index 84c7aaf..693a6d1 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ logs/ # Claude Code local agent state .claude/ + +# Wallpaper source drop (baked copy lives in airootfs/usr/share/backgrounds) +/Bread Background.png diff --git a/build-local.sh b/build-local.sh new file mode 100755 index 0000000..3015be0 --- /dev/null +++ b/build-local.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# Local BOS ISO build for hermes (native Arch — no container needed). +# +# Builds straight from the working tree in ./iso. Two speedups vs the hestia +# container build: +# * runs natively (hermes is Arch; hestia needed a dockerised Arch) +# * no 2 GB scp afterwards — the ISO lands here, where we test it +# +# FAST_BUILD=1 zstd squashfs instead of xz -9e: compresses many times +# faster at the cost of a slightly larger image. Dev only. +# +# Usage: sudo ./build-local.sh # release-quality xz +# sudo FAST_BUILD=1 ./build-local.sh # fast dev iteration +set -euo pipefail + +REPO="$(cd "$(dirname "$0")" && pwd)" +WORK=/tmp/bos-work +OUT="${OUT:-$REPO/out}" + +# Build against a throwaway copy of the profile so the working tree stays clean +# when FAST_BUILD / the registry rewrite mutate profile files. +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. +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" + +if [ "${FAST_BUILD:-0}" = "1" ]; then + echo "=== FAST_BUILD: squashfs -> zstd level 6 ===" + sed -i "s#^airootfs_image_tool_options=.*#airootfs_image_tool_options=('-comp' 'zstd' '-Xcompression-level' '6' '-b' '1M')#" "$STAGE/profiledef.sh" +fi +grep airootfs_image_tool_options "$STAGE/profiledef.sh" + +# --- Bake this laptop's bakery-installed bread ecosystem into /etc/skel ------- +# The bread apps are managed by bakery (which fetches release binaries from +# GitHub), not pacman. bakery needs DNS at install time, which the live/installed +# image doesn't have — so instead of running bakery on the target, we copy the +# exact binaries + bakery manifest this laptop already has into skel. Every user +# 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) +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" +BAKERY_CACHE="$LAPTOP_HOME/.cache/bakery" +SKEL="$STAGE/airootfs/etc/skel" +echo "=== baking bakery bread ecosystem from $LAPTOP_HOME ===" +install -d -m 0755 "$SKEL/.local/bin" "$SKEL/.local/state/bakery" "$SKEL/.cache/bakery" +for b in "${BREAD_BINS[@]}"; do + install -m 0755 "$BAKERY_BIN/$b" "$SKEL/.local/bin/$b" +done +install -m 0644 "$BAKERY_STATE/installed.json" "$SKEL/.local/state/bakery/installed.json" +# bakery fetches its package index from dl.breadway.dev (then a GitHub fallback), +# but falls back to a cached index when both are unreachable. With no network/DNS +# in the live/installed image, even `bakery list` errors unless that cache exists, +# so bake it in too — then bakery works fully offline (list/info from cache; +# install/update still need network, as expected). +install -m 0644 "$BAKERY_CACHE/index.json" "$SKEL/.cache/bakery/index.json" +echo "baked: $(ls "$SKEL/.local/bin")" + +# 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 +# baked binary right after the array opener (keeps the binary list in one place). +perm_file="$(mktemp)" +for b in "${BREAD_BINS[@]}"; do + printf ' ["/etc/skel/.local/bin/%s"]="0:0:755"\n' "$b" >>"$perm_file" +done +sed -i "/^file_permissions=(/r $perm_file" "$STAGE/profiledef.sh" +rm -f "$perm_file" +echo "=== file_permissions after injection ==="; grep -A14 '^file_permissions=(' "$STAGE/profiledef.sh" + +# Pin one timestamp for the whole build. Without this, mkarchiso derives the +# boot-config UUID (%ARCHISO_UUID%) when it starts and the iso9660 volume UUID +# when xorriso writes the image at the end — on a slow build these diverge by +# the build duration, so the initramfs searches /dev/disk/by-uuid/, +# never finds the medium, and drops to a recovery shell. Fixing the epoch makes +# both derive from the same instant (and makes builds reproducible). +export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(date +%s)}" +echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH ($(date -u -d "@$SOURCE_DATE_EPOCH" +%Y-%m-%d-%H-%M-%S-00))" + +echo "=== running mkarchiso ===" +rm -rf "$WORK" && mkdir -p "$OUT" +mkarchiso -v -w "$WORK" -o "$OUT" "$STAGE" + +echo "=== RESULT ===" +if ls -lh "$OUT"/*.iso 2>/dev/null; then echo "ISO BUILT OK -> $OUT"; else echo "ISO BUILD FAILED"; exit 1; fi diff --git a/iso/airootfs/etc/calamares/branding/bos/branding.desc b/iso/airootfs/etc/calamares/branding/bos/branding.desc index ff72dd7..91c929f 100644 --- a/iso/airootfs/etc/calamares/branding/bos/branding.desc +++ b/iso/airootfs/etc/calamares/branding/bos/branding.desc @@ -23,7 +23,7 @@ slideshow: "show.qml" slideshowAPI: 2 style: - sidebarBackground: "#3b4252" - sidebarText: "#eceff4" - sidebarTextSelect: "#5e81ac" - sidebarTextHighlight: "#eceff4" + sidebarBackground: "#230b00" + sidebarText: "#f1dcbd" + sidebarTextSelect: "#EAB672" + sidebarTextHighlight: "#ffffff" diff --git a/iso/airootfs/etc/calamares/branding/bos/stylesheet.qss b/iso/airootfs/etc/calamares/branding/bos/stylesheet.qss new file mode 100644 index 0000000..f83b887 --- /dev/null +++ b/iso/airootfs/etc/calamares/branding/bos/stylesheet.qss @@ -0,0 +1,23 @@ +/* BOS Calamares styling. + * + * The branding `style:` sidebar keys were being overridden, leaving the + * step tabs invisible. This forces them: bread-palette sidebar with clearly + * legible step labels. (A full installer retheme is tracked separately.) + */ + +/* Left sidebar / progress steps */ +#sidebarApp { + background-color: #230b00; +} + +#sidebarApp QLabel { + color: #f1dcbd; + font-size: 11pt; + padding: 3px 0; +} + +/* Logo at the top of the sidebar — keep aspect ratio, don't stretch */ +#logoApp { + qproperty-alignment: AlignCenter; + margin: 14px 8px; +} diff --git a/iso/airootfs/etc/calamares/modules/mount.conf b/iso/airootfs/etc/calamares/modules/mount.conf index 767bf57..4218ca7 100644 --- a/iso/airootfs/etc/calamares/modules/mount.conf +++ b/iso/airootfs/etc/calamares/modules/mount.conf @@ -8,3 +8,32 @@ mountOptions: options: [noatime, "compress=zstd", "space_cache=v2"] - filesystem: vfat options: [umask=0077] + +# API filesystems mounted into the target so chroot steps work. Without these +# the chroot has no /proc or /dev and `mkinitcpio` aborts ("/proc must be +# mounted!" / "/dev must be mounted!") and grub-install can't probe properly. +# All are real-fs mounts (not bind) — Calamares 3.4.2 here applies fs-type mounts +# reliably but not bind-type ones, so /dev uses a fresh devtmpfs (which still +# exposes all device nodes). extraMountsEfi adds efivars on UEFI so grub-install +# can write an NVRAM boot entry. +extraMounts: + - device: proc + fs: proc + mountPoint: /proc + - device: sys + fs: sysfs + mountPoint: /sys + - device: udev + fs: devtmpfs + mountPoint: /dev + - device: devpts + fs: devpts + mountPoint: /dev/pts + - device: tmpfs + fs: tmpfs + mountPoint: /run + +extraMountsEfi: + - device: efivarfs + fs: efivarfs + mountPoint: /sys/firmware/efi/efivars diff --git a/iso/airootfs/etc/calamares/modules/plymouthcfg.conf b/iso/airootfs/etc/calamares/modules/plymouthcfg.conf new file mode 100644 index 0000000..d9aeb16 --- /dev/null +++ b/iso/airootfs/etc/calamares/modules/plymouthcfg.conf @@ -0,0 +1,6 @@ +--- +# Boot-splash theme for the installed system. Calamares sets this as the default +# plymouth theme and signals initcpiocfg to add the plymouth hook to the +# initramfs. The post-install script also enforces the hook + a quiet/splash +# cmdline as a belt-and-suspenders. +plymouth_theme: bos diff --git a/iso/airootfs/etc/calamares/modules/shellprocess.conf b/iso/airootfs/etc/calamares/modules/shellprocess.conf index 3f5366c..b05cee1 100644 --- a/iso/airootfs/etc/calamares/modules/shellprocess.conf +++ b/iso/airootfs/etc/calamares/modules/shellprocess.conf @@ -1,9 +1,10 @@ --- -# Calamares defaults shellprocess to a 10-second timeout. post-install.sh runs -# mkinitcpio, grub-install, grub-mkconfig, snapper setup and a networked bakery -# install — minutes of work — so without a generous timeout it gets killed -# partway (leaving /boot and the ESP half-populated → unbootable system). The -# leading "-" keeps a non-zero exit non-fatal to the install. -timeout: 1800 +# BOS finalization, run after the native initcpio + bootloader modules. It does +# only fast, non-boot-critical work (live-medium cleanup, snapper, services, +# dotfiles), so the shellprocess timeout can no longer leave the system +# unbootable — the boot-critical steps are owned by dedicated Calamares modules. +# A generous timeout is kept as a safety margin, and the leading "-" keeps a +# non-zero exit non-fatal to the install. +timeout: 600 script: - "-/usr/bin/bash /etc/calamares/post-install.sh" diff --git a/iso/airootfs/etc/calamares/post-install.sh b/iso/airootfs/etc/calamares/post-install.sh index 6e9a7d8..2f27409 100644 --- a/iso/airootfs/etc/calamares/post-install.sh +++ b/iso/airootfs/etc/calamares/post-install.sh @@ -1,7 +1,11 @@ #!/bin/bash -# Runs inside the installed-system chroot (Calamares shellprocess, after the -# bootloader step). Best-effort: a single failure must not abort the rest, so -# we deliberately do NOT use `set -e`. +# BOS-specific finalization, run inside the installed-system chroot (Calamares +# shellprocess), AFTER the native initcpio module has built the initramfs. The +# kernel (shellprocess@kernel) and initramfs (initcpio) are in place by now, so +# this script installs GRUB and does the rest of setup. Calamares' own +# `bootloader`/`grubcfg` modules are NOT used — in this archiso layout they leave +# the ESP empty and abort; the explicit grub-install below is verified to boot. +# Best-effort: do NOT use `set -e`; a single failure here must not abort the rest. set -uo pipefail MAIN_USER="$(getent passwd 1000 | cut -d: -f1 || true)" @@ -20,30 +24,42 @@ userdel -r liveuser 2>/dev/null || true passwd -l root || true # --------------------------------------------------------------------------- -# Rebuild the initramfs for a real install — the live image ships the archiso -# hooks, which would send the installed system into the live-boot path. +# 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. # --------------------------------------------------------------------------- -rm -f /etc/mkinitcpio.conf.d/archiso.conf -cat >/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 -mkinitcpio -P || echo "WARN: mkinitcpio regeneration failed" +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 + sed -i 's/^\(HOOKS=.*\budev\b\)/\1 plymouth/' /etc/mkinitcpio.conf \ + || echo "WARN: adding plymouth hook failed" + fi + fi + # 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). + if ! grep -q 'splash' /etc/default/grub 2>/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" +fi # --------------------------------------------------------------------------- -# Install GRUB ourselves. Calamares' bootloader module runs before the kernel -# and initramfs exist (archiso keeps them out of the squashfs; shellprocess -# @kernel only lays vmlinuz down just beforehand), so its grub-install/config -# leaves the ESP empty. Redo it here, now that /boot is fully populated. -# -# Two passes: the standard NVRAM entry, plus a --removable copy to -# EFI/BOOT/BOOTX64.EFI so firmware that lost/never wrote an NVRAM entry (the -# "no boot device / screen just refreshes" failure) still finds a bootloader. +# 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 +# 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. # --------------------------------------------------------------------------- if command -v grub-install &>/dev/null; then grub-install --target=x86_64-efi --efi-directory=/boot/efi \ @@ -53,8 +69,6 @@ if command -v grub-install &>/dev/null; then --removable --recheck \ || echo "WARN: grub-install (removable) failed" fi - -# Refresh GRUB so it references the kernel + rebuilt initramfs. if command -v grub-mkconfig &>/dev/null; then grub-mkconfig -o /boot/grub/grub.cfg || echo "WARN: grub-mkconfig failed" fi @@ -76,22 +90,39 @@ if command -v snapper &>/dev/null; then fi # --------------------------------------------------------------------------- -# System services. +# System services. Enable each one INDEPENDENTLY: `systemctl enable a b c` +# resolves every unit first and enables NONE if any one can't be loaded, so a +# single wrong/absent unit name would silently leave NetworkManager (etc.) +# disabled. The loop isolates failures to the offending unit. +# greetd — graphical login (shipped disabled; live uses tty autologin) +# grub-btrfsd — regenerates GRUB snapshot entries (the unit is grub-btrfsd.service, +# NOT grub-btrfs.path, which no longer exists) # --------------------------------------------------------------------------- -systemctl enable NetworkManager bluetooth snapper-cleanup.timer grub-btrfs.path \ - || echo "WARN: enabling some services failed" +for unit in NetworkManager.service bluetooth.service systemd-timesyncd.service \ + tlp.service greetd.service snapper-cleanup.timer grub-btrfsd.service \ + fstrim.timer; do + systemctl enable "$unit" || echo "WARN: failed to enable $unit" +done +systemctl set-default graphical.target || echo "WARN: set-default graphical failed" -# The bread ecosystem (bread, breadbar, breadbox, breadcrumbs, breadpad, -# bos-settings) is baked into the squashfs and already copied onto the target by -# unpackfs — no install step needed here, and the install works fully offline. +# 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 +# home below, so the install works fully offline with no DNS for bakery/GitHub. +# bos-settings is the only pacman bread package and was installed by unpackfs. # --------------------------------------------------------------------------- -# Deploy dotfiles into the user's home (don't clobber existing files). +# Deploy dotfiles + the bakery bread ecosystem into the user's home (Calamares +# already seeds from /etc/skel, but copy explicitly too so a fresh install is +# self-contained even if the users module skips skel). Don't clobber existing. # --------------------------------------------------------------------------- -if [[ -n "$MAIN_USER" && -d /etc/skel/.config ]]; then - mkdir -p "/home/$MAIN_USER/.config" - cp -rn /etc/skel/.config/. "/home/$MAIN_USER/.config/" || true - chown -R "$MAIN_USER:$MAIN_USER" "/home/$MAIN_USER/.config" || true +if [[ -n "$MAIN_USER" && -d /etc/skel ]]; then + for d in .config .local .cache; do + [[ -d "/etc/skel/$d" ]] || continue + mkdir -p "/home/$MAIN_USER/$d" + cp -rn "/etc/skel/$d/." "/home/$MAIN_USER/$d/" || true + chown -R "$MAIN_USER:$MAIN_USER" "/home/$MAIN_USER/$d" || true + done sudo -u "$MAIN_USER" xdg-user-dirs-update || true fi diff --git a/iso/airootfs/etc/calamares/settings.conf b/iso/airootfs/etc/calamares/settings.conf index bb6f01c..f97ae87 100644 --- a/iso/airootfs/etc/calamares/settings.conf +++ b/iso/airootfs/etc/calamares/settings.conf @@ -29,8 +29,21 @@ sequence: - networkcfg - hwclock - packages + # archiso strips the kernel from the squashfs; stage it, drop the archiso + # initramfs config, and write a stock mkinitcpio preset before initcpio runs. - shellprocess@kernel - - bootloader + # plymouthcfg sets the boot-splash theme and flags plymouth in use, so + # initcpiocfg adds the plymouth hook to the initramfs that initcpio builds. + - plymouthcfg + # Native initramfs generation (works reliably here). The native `bootloader` + # and `grubcfg` modules do NOT — in this archiso layout they leave the ESP + # empty and abort the install, so GRUB is installed explicitly in + # post-install.sh instead (grub-install --removable + NVRAM + grub-mkconfig, + # the sequence verified to produce a bootable system). + - initcpiocfg + - initcpio + # BOS finalization: GRUB install + cleanup + snapper + services + dotfiles. + # All fast, and runs after initcpio so /boot has the kernel + initramfs. - shellprocess - umount - show: diff --git a/iso/airootfs/etc/greetd/config.toml b/iso/airootfs/etc/greetd/config.toml new file mode 100644 index 0000000..4488779 --- /dev/null +++ b/iso/airootfs/etc/greetd/config.toml @@ -0,0 +1,12 @@ +# greetd drives login on the INSTALLED system. It is shipped DISABLED in the +# squashfs and enabled by post-install.sh; the live ISO instead autologins +# liveuser on tty1 (see getty@tty1 drop-in) so the installer comes straight up. +# +# tuigreet shows a minimal greeter, then launches the BOS Hyprland session via +# bos-session (which fixes up PATH for the bakery bread apps). +[terminal] +vt = 1 + +[default_session] +command = "tuigreet --remember --time --cmd /usr/local/bin/bos-session" +user = "greeter" diff --git a/iso/airootfs/etc/pacman.conf b/iso/airootfs/etc/pacman.conf new file mode 100644 index 0000000..90e4517 --- /dev/null +++ b/iso/airootfs/etc/pacman.conf @@ -0,0 +1,44 @@ +# +# BOS pacman.conf — used during ISO build and installed to the target system. +# Based on the standard Arch Linux pacman.conf. +# + +[options] +HoldPkg = pacman glibc +Architecture = auto +CheckSpace +ParallelDownloads = 5 + +Color +VerbosePkgLists +ILoveCandy + +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional + +[core] +Include = /etc/pacman.d/mirrorlist + +[extra] +Include = /etc/pacman.d/mirrorlist + +[multilib] +Include = /etc/pacman.d/mirrorlist + +# ----------------------------------------------------------------------- +# Breadway custom repo — provides: bakery and the bread ecosystem packages +# (bread, breadbar, breadbox, breadcrumbs, breadpad, bos-settings). +# (calamares comes from the official extra repo, not here.) +# +# Packages are published to the Forgejo Arch registry (group "os") by the +# .forgejo/workflows/package.yml workflow in each repo, on tag push. +# +# 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. +# ----------------------------------------------------------------------- +# The section name must match Forgejo's served db filename +# ({owner}.{group}.{domain}.db) — pacman fetches "
.db" from Server. +[Breadway.os.git.breadway.dev] +SigLevel = Never +Server = https://git.breadway.dev/api/packages/Breadway/arch/os/$arch diff --git a/iso/airootfs/etc/pacman.d/mirrorlist b/iso/airootfs/etc/pacman.d/mirrorlist new file mode 100644 index 0000000..1199b26 --- /dev/null +++ b/iso/airootfs/etc/pacman.d/mirrorlist @@ -0,0 +1,11 @@ +# BOS default mirrorlist. +# +# geo.mirror.pkgbuild.com is the official Arch geo-IP redirect — it routes to a +# nearby mirror anywhere in the world, so pacman works out of the box without +# region-specific configuration. The others are reliable global fallbacks. +# +# To optimise for your location/speed later: sudo reflector --save \ +# /etc/pacman.d/mirrorlist --protocol https --latest 20 --sort rate +Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch +Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch +Server = https://mirror.leaseweb.net/archlinux/$repo/os/$arch diff --git a/iso/airootfs/etc/profile.d/bos-local-bin.sh b/iso/airootfs/etc/profile.d/bos-local-bin.sh new file mode 100644 index 0000000..642af43 --- /dev/null +++ b/iso/airootfs/etc/profile.d/bos-local-bin.sh @@ -0,0 +1,9 @@ +# Put the per-user bakery bin dir on PATH. The bread ecosystem (breadd, breadbar, +# breadbox, …) is installed there by bakery, and the Hyprland session launches +# them via `exec-once`, which resolves against the PATH it inherits from the +# login shell. Arch's stock /etc/profile does not add ~/.local/bin, so do it here +# for every login shell (live user and installed user alike). +case ":$PATH:" in + *":$HOME/.local/bin:"*) ;; + *) export PATH="$HOME/.local/bin:$PATH" ;; +esac diff --git a/iso/airootfs/etc/skel/.cache/wal/colors.json b/iso/airootfs/etc/skel/.cache/wal/colors.json new file mode 100644 index 0000000..2b32390 --- /dev/null +++ b/iso/airootfs/etc/skel/.cache/wal/colors.json @@ -0,0 +1,28 @@ +{ + "wallpaper": "/usr/share/backgrounds/bos/bread-background.png", + "alpha": "100", + + "special": { + "background": "#0c0c0c", + "foreground": "#e8e8e8", + "cursor": "#eab672" + }, + "colors": { + "color0": "#1a1a1a", + "color1": "#b98749", + "color2": "#cd9450", + "color3": "#e3a85c", + "color4": "#eab672", + "color5": "#f6c477", + "color6": "#eabe82", + "color7": "#d8d8d8", + "color8": "#3a3a3a", + "color9": "#b98749", + "color10": "#cd9450", + "color11": "#e3a85c", + "color12": "#eab672", + "color13": "#f6c477", + "color14": "#eabe82", + "color15": "#f5f5f5" + } +} diff --git a/iso/airootfs/etc/skel/.config/gtk-3.0/settings.ini b/iso/airootfs/etc/skel/.config/gtk-3.0/settings.ini new file mode 100644 index 0000000..429393f --- /dev/null +++ b/iso/airootfs/etc/skel/.config/gtk-3.0/settings.ini @@ -0,0 +1,3 @@ +[Settings] +gtk-application-prefer-dark-theme=1 +gtk-theme-name=Adwaita-dark diff --git a/iso/airootfs/etc/skel/.config/gtk-4.0/settings.ini b/iso/airootfs/etc/skel/.config/gtk-4.0/settings.ini new file mode 100644 index 0000000..429393f --- /dev/null +++ b/iso/airootfs/etc/skel/.config/gtk-4.0/settings.ini @@ -0,0 +1,3 @@ +[Settings] +gtk-application-prefer-dark-theme=1 +gtk-theme-name=Adwaita-dark diff --git a/iso/airootfs/etc/skel/.config/hypr/hypridle.conf b/iso/airootfs/etc/skel/.config/hypr/hypridle.conf new file mode 100644 index 0000000..aacd512 --- /dev/null +++ b/iso/airootfs/etc/skel/.config/hypr/hypridle.conf @@ -0,0 +1,37 @@ +# Idle management (hypridle). Conservative, mainstream-OS-like defaults: +# dim -> screen off -> lock -> suspend, all respecting idle inhibitors (so media +# playback, etc. won't dim or suspend the machine). Vendor-neutral: nothing here +# is Intel/AMD specific. +general { + lock_cmd = pidof hyprlock || hyprlock + before_sleep_cmd = loginctl lock-session + after_sleep_cmd = hyprctl dispatch dpms on + ignore_dbus_inhibit = false +} + +# Dim the backlight after 5 minutes (restored on activity). No-op on hardware +# without a backlight (desktops / VMs). +listener { + timeout = 300 + on-timeout = brightnessctl -s set 10% + on-resume = brightnessctl -r +} + +# Turn the display off after 8 minutes. +listener { + timeout = 480 + on-timeout = hyprctl dispatch dpms off + on-resume = hyprctl dispatch dpms on +} + +# Lock shortly after the screen turns off. +listener { + timeout = 510 + on-timeout = loginctl lock-session +} + +# Suspend after 20 minutes of inactivity (skipped while something inhibits idle). +listener { + timeout = 1200 + on-timeout = systemctl suspend +} diff --git a/iso/airootfs/etc/skel/.config/hypr/hyprland.conf b/iso/airootfs/etc/skel/.config/hypr/hyprland.conf deleted file mode 100644 index e0a4889..0000000 --- a/iso/airootfs/etc/skel/.config/hypr/hyprland.conf +++ /dev/null @@ -1,55 +0,0 @@ -monitor=,preferred,auto,1 - -exec-once = breadd -exec-once = breadbar -exec-once = breadbox-sync - -source = ~/.config/hypr/keybinds.conf - -general { - gaps_in = 5 - gaps_out = 10 - border_size = 2 - col.active_border = rgba(88c0d0ff) - col.inactive_border = rgba(4c566aff) - layout = dwindle -} - -decoration { - rounding = 8 - blur { - enabled = true - size = 6 - passes = 2 - } - shadow { - enabled = true - range = 12 - render_power = 3 - } -} - -animations { - enabled = true - bezier = ease, 0.25, 0.1, 0.25, 1.0 - animation = windows, 1, 4, ease - animation = fade, 1, 4, ease - animation = workspaces, 1, 5, ease -} - -input { - kb_layout = us - follow_mouse = 1 - touchpad { - natural_scroll = true - } -} - -dwindle { - preserve_split = true -} - -misc { - disable_hyprland_logo = true - disable_splash_rendering = true -} diff --git a/iso/airootfs/etc/skel/.config/hypr/hyprland.lua b/iso/airootfs/etc/skel/.config/hypr/hyprland.lua new file mode 100644 index 0000000..36a4657 --- /dev/null +++ b/iso/airootfs/etc/skel/.config/hypr/hyprland.lua @@ -0,0 +1,168 @@ +-- BOS Hyprland configuration — native Lua config (Hyprland 0.55+). +-- hyprlang (.conf) is deprecated; this uses the built-in `hl` API. +-- Single-file and non-modular by design. Reference: https://wiki.hypr.land/ + +local mod = "SUPER" + +-- --------------------------------------------------------------------------- +-- Monitors — generic default that works on any hardware. +-- --------------------------------------------------------------------------- +hl.monitor({ output = "", mode = "preferred", position = "auto", scale = "auto" }) + +-- --------------------------------------------------------------------------- +-- Core settings. +-- --------------------------------------------------------------------------- +hl.config({ + general = { + gaps_in = 5, + gaps_out = 10, + border_size = 2, + col = { + active_border = "rgba(88c0d0ff)", + inactive_border = "rgba(4c566aff)", + }, + layout = "dwindle", + resize_on_border = true, + }, + decoration = { + rounding = 8, + active_opacity = 1.0, + inactive_opacity = 1.0, + blur = { + enabled = true, + size = 6, + passes = 2, + new_optimizations = true, + }, + shadow = { + enabled = true, + range = 12, + render_power = 3, + }, + }, + input = { + kb_layout = "us", + follow_mouse = 1, + touchpad = { natural_scroll = true }, + }, + dwindle = { + preserve_split = true, + }, + animations = { + enabled = true, + }, + misc = { + disable_hyprland_logo = true, + disable_splash_rendering = true, + }, +}) + +-- --------------------------------------------------------------------------- +-- Environment (vendor-neutral; no GPU-specific vars so it works on Intel/AMD). +-- --------------------------------------------------------------------------- +hl.env("XCURSOR_SIZE", "24") +hl.env("HYPRCURSOR_SIZE", "24") +hl.env("MOZ_ENABLE_WAYLAND", "1") +hl.env("QT_QPA_PLATFORM", "wayland;xcb") +hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1") +hl.env("SDL_VIDEODRIVER", "wayland") +hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto") +hl.env("_JAVA_AWT_WM_NONREPARENTING", "1") + +-- kitty sets its own background_opacity (see kitty.conf), so the global blur +-- above blurs behind the terminal while keeping text fully opaque. + +-- --------------------------------------------------------------------------- +-- Standard BOS keybinds (SUPER = mod). +-- --------------------------------------------------------------------------- +-- Apps / window management +hl.bind(mod .. " + RETURN", hl.dsp.exec_cmd("kitty")) +hl.bind(mod .. " + BACKSPACE", hl.dsp.window.close()) +hl.bind(mod .. " + SPACE", hl.dsp.exec_cmd("breadbox")) +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 .. " + 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 .. " + T", hl.dsp.layout("togglesplit")) +hl.bind(mod .. " + Tab", hl.dsp.focus({ urgent_or_last = true })) +hl.bind(mod .. " + N", hl.dsp.exit()) + +-- Screenshots (grim + slurp + wl-clipboard) +hl.bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd([[bash -c 'mkdir -p ~/Pictures/Screenshots && grim -g "$(slurp)" ~/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png']])) +hl.bind(mod .. " + SHIFT + C", hl.dsp.exec_cmd([[bash -c 'grim -g "$(slurp)" - | wl-copy']])) +hl.bind(mod .. " + SHIFT + P", hl.dsp.exec_cmd([[bash -c 'mkdir -p ~/Pictures/Screenshots && grim ~/Pictures/Screenshots/$(date +%Y%m%d-%H%M%S).png']])) + +-- Focus (directional) +hl.bind(mod .. " + left", hl.dsp.focus({ direction = "left" })) +hl.bind(mod .. " + right", hl.dsp.focus({ direction = "right" })) +hl.bind(mod .. " + up", hl.dsp.focus({ direction = "up" })) +hl.bind(mod .. " + down", hl.dsp.focus({ direction = "down" })) + +-- Move window (directional, vim keys) +hl.bind(mod .. " + SHIFT + h", hl.dsp.window.move({ direction = "left" })) +hl.bind(mod .. " + SHIFT + j", hl.dsp.window.move({ direction = "down" })) +hl.bind(mod .. " + SHIFT + k", hl.dsp.window.move({ direction = "up" })) +hl.bind(mod .. " + SHIFT + l", hl.dsp.window.move({ direction = "right" })) + +-- Resize active window (arrows) +hl.bind(mod .. " + SHIFT + right", hl.dsp.window.resize({ x = 30, y = 0, relative = true }), { repeating = true }) +hl.bind(mod .. " + SHIFT + left", hl.dsp.window.resize({ x = -30, y = 0, relative = true }), { repeating = true }) +hl.bind(mod .. " + SHIFT + up", hl.dsp.window.resize({ x = 0, y = -30, relative = true }), { repeating = true }) +hl.bind(mod .. " + SHIFT + down", hl.dsp.window.resize({ x = 0, y = 30, relative = true }), { repeating = true }) + +-- Workspaces 1–10 (0 = workspace 10) +for i = 1, 10 do + local key = tostring(i % 10) + hl.bind(mod .. " + " .. key, hl.dsp.focus({ workspace = i })) + hl.bind(mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i })) +end + +-- Workspace cycling +hl.bind(mod .. " + bracketright", hl.dsp.focus({ workspace = "e+1" })) +hl.bind(mod .. " + bracketleft", hl.dsp.focus({ workspace = "e-1" })) +hl.bind(mod .. " + SHIFT + bracketright", hl.dsp.window.move({ workspace = "e+1" })) +hl.bind(mod .. " + SHIFT + bracketleft", hl.dsp.window.move({ workspace = "e-1" })) + +-- Mouse +hl.bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" })) +hl.bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" })) +hl.bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true }) +hl.bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) + +-- Media / hardware keys (work locked, i.e. on the lock screen too) +hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true }) +hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true }) +hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true }) +hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true }) +hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%+"), { locked = true, repeating = true }) +hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl -e4 -n2 set 5%-"), { locked = true, repeating = true }) +hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true }) +hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true }) +hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) + +-- --------------------------------------------------------------------------- +-- Autostart. polkit agent + the bread ecosystem + idle daemon + wallpaper. +-- (bos-live-setup appends the live-installer launch below this on the ISO.) +-- --------------------------------------------------------------------------- +hl.on("hyprland.start", function() + local startup = { + -- Prefer dark for GTK4/libadwaita apps (GTK3 uses settings.ini); without + -- this nautilus/breadbox render in light mode. + "gsettings set org.gnome.desktop.interface color-scheme prefer-dark", + "gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark", + "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1", + "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", + "breadbar", + "breadbox-sync", + "hypridle", + } + for _, cmd in ipairs(startup) do + hl.dispatch(hl.dsp.exec_cmd(cmd)) + end +end) diff --git a/iso/airootfs/etc/skel/.config/hypr/hyprlock.conf b/iso/airootfs/etc/skel/.config/hypr/hyprlock.conf new file mode 100644 index 0000000..30903df --- /dev/null +++ b/iso/airootfs/etc/skel/.config/hypr/hyprlock.conf @@ -0,0 +1,28 @@ +# Lock screen (hyprlock). Solid dark background (no runtime-generated wallpaper +# dependency), accent matched to the Hyprland border colour. +general { + hide_cursor = true +} + +background { + monitor = + color = rgba(46, 52, 64, 1.0) + blur_passes = 0 +} + +input-field { + monitor = + size = 20%, 5% + outline_thickness = 3 + inner_color = rgba(0, 0, 0, 0.2) + outer_color = rgba(136, 192, 208, 0.8) + check_color = rgba(120, 220, 140, 0.95) + fail_color = rgba(255, 90, 90, 0.95) + font_color = rgba(255, 255, 255, 0.95) + fade_on_empty = false + rounding = 12 + placeholder_text = Password… + position = 0, -20 + halign = center + valign = center +} diff --git a/iso/airootfs/etc/skel/.config/hypr/keybinds.conf b/iso/airootfs/etc/skel/.config/hypr/keybinds.conf deleted file mode 100644 index 7cf8cdd..0000000 --- a/iso/airootfs/etc/skel/.config/hypr/keybinds.conf +++ /dev/null @@ -1,58 +0,0 @@ -$mod = SUPER - -# App launchers -bind = $mod, Space, exec, breadbox -bind = $mod, N, exec, breadpad -bind = $mod, M, exec, breadman -bind = $mod, S, exec, bos-settings - -# Core -bind = $mod, Return, exec, foot -bind = $mod, Q, killactive -bind = $mod SHIFT, E, exit -bind = $mod, F, fullscreen - -# Focus -bind = $mod, H, movefocus, l -bind = $mod, L, movefocus, r -bind = $mod, K, movefocus, u -bind = $mod, J, movefocus, d - -# Move windows -bind = $mod SHIFT, H, movewindow, l -bind = $mod SHIFT, L, movewindow, r -bind = $mod SHIFT, K, movewindow, u -bind = $mod SHIFT, J, movewindow, d - -# Workspaces -bind = $mod, 1, workspace, 1 -bind = $mod, 2, workspace, 2 -bind = $mod, 3, workspace, 3 -bind = $mod, 4, workspace, 4 -bind = $mod, 5, workspace, 5 - -bind = $mod SHIFT, 1, movetoworkspace, 1 -bind = $mod SHIFT, 2, movetoworkspace, 2 -bind = $mod SHIFT, 3, movetoworkspace, 3 -bind = $mod SHIFT, 4, movetoworkspace, 4 -bind = $mod SHIFT, 5, movetoworkspace, 5 - -# Scroll through workspaces -bind = $mod, mouse_down, workspace, e+1 -bind = $mod, mouse_up, workspace, e-1 - -# Mouse binds -bindm = $mod, mouse:272, movewindow -bindm = $mod, mouse:273, resizewindow - -# Volume -bind = , XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ -bind = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- -bind = , XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle - -# Brightness -bind = , XF86MonBrightnessUp, exec, brightnessctl set 5%+ -bind = , XF86MonBrightnessDown, exec, brightnessctl set 5%- - -# Screenshot -bind = , Print, exec, grimblast copy area diff --git a/iso/airootfs/etc/skel/.config/kitty/kitty.conf b/iso/airootfs/etc/skel/.config/kitty/kitty.conf new file mode 100644 index 0000000..d16b128 --- /dev/null +++ b/iso/airootfs/etc/skel/.config/kitty/kitty.conf @@ -0,0 +1,13 @@ +# 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 + +font_family JetBrains Mono +font_size 11.0 +cursor_shape beam +scrollback_lines 10000 +enable_audio_bell no +window_padding_width 8 +confirm_os_window_close 0 diff --git a/iso/airootfs/etc/systemd/logind.conf.d/90-bos-power.conf b/iso/airootfs/etc/systemd/logind.conf.d/90-bos-power.conf new file mode 100644 index 0000000..f3bd2bb --- /dev/null +++ b/iso/airootfs/etc/systemd/logind.conf.d/90-bos-power.conf @@ -0,0 +1,7 @@ +# Lid behaviour: suspend on close (on battery or AC), but ignore the lid when +# docked / an external display is connected so closing the laptop with a monitor +# attached keeps the session running. Mainstream-desktop default. +[Login] +HandleLidSwitch=suspend +HandleLidSwitchExternalPower=suspend +HandleLidSwitchDocked=ignore diff --git a/iso/airootfs/etc/systemd/system/multi-user.target.wants/NetworkManager.service b/iso/airootfs/etc/systemd/system/multi-user.target.wants/NetworkManager.service new file mode 120000 index 0000000..e874a9b --- /dev/null +++ b/iso/airootfs/etc/systemd/system/multi-user.target.wants/NetworkManager.service @@ -0,0 +1 @@ +/usr/lib/systemd/system/NetworkManager.service \ No newline at end of file diff --git a/iso/airootfs/usr/local/bin/bos-copy-kernel b/iso/airootfs/usr/local/bin/bos-copy-kernel index 1d75e46..ee23c30 100755 --- a/iso/airootfs/usr/local/bin/bos-copy-kernel +++ b/iso/airootfs/usr/local/bin/bos-copy-kernel @@ -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" diff --git a/iso/airootfs/usr/local/bin/bos-live-setup b/iso/airootfs/usr/local/bin/bos-live-setup index d90f199..0fbe5bd 100644 --- a/iso/airootfs/usr/local/bin/bos-live-setup +++ b/iso/airootfs/usr/local/bin/bos-live-setup @@ -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 diff --git a/iso/airootfs/usr/local/bin/bos-session b/iso/airootfs/usr/local/bin/bos-session new file mode 100644 index 0000000..8fecd2c --- /dev/null +++ b/iso/airootfs/usr/local/bin/bos-session @@ -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 diff --git a/iso/airootfs/usr/share/backgrounds/bos/bread-background.png b/iso/airootfs/usr/share/backgrounds/bos/bread-background.png new file mode 100644 index 0000000..12dee74 Binary files /dev/null and b/iso/airootfs/usr/share/backgrounds/bos/bread-background.png differ diff --git a/iso/airootfs/usr/share/fonts/TTF/VarelaRound-Regular.ttf b/iso/airootfs/usr/share/fonts/TTF/VarelaRound-Regular.ttf new file mode 100644 index 0000000..3a54b78 Binary files /dev/null and b/iso/airootfs/usr/share/fonts/TTF/VarelaRound-Regular.ttf differ diff --git a/iso/airootfs/usr/share/plymouth/themes/bos/bos.plymouth b/iso/airootfs/usr/share/plymouth/themes/bos/bos.plymouth new file mode 100644 index 0000000..76ec2ea --- /dev/null +++ b/iso/airootfs/usr/share/plymouth/themes/bos/bos.plymouth @@ -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 diff --git a/iso/airootfs/usr/share/plymouth/themes/bos/bos.script b/iso/airootfs/usr/share/plymouth/themes/bos/bos.script new file mode 100644 index 0000000..e201f5d --- /dev/null +++ b/iso/airootfs/usr/share/plymouth/themes/bos/bos.script @@ -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); diff --git a/iso/airootfs/usr/share/plymouth/themes/bos/logo.png b/iso/airootfs/usr/share/plymouth/themes/bos/logo.png new file mode 100644 index 0000000..fb96675 Binary files /dev/null and b/iso/airootfs/usr/share/plymouth/themes/bos/logo.png differ diff --git a/iso/airootfs/usr/share/plymouth/themes/bos/spinner.png b/iso/airootfs/usr/share/plymouth/themes/bos/spinner.png new file mode 100644 index 0000000..d82452c Binary files /dev/null and b/iso/airootfs/usr/share/plymouth/themes/bos/spinner.png differ diff --git a/iso/packages.x86_64 b/iso/packages.x86_64 index 37c757c..637ca06 100644 --- a/iso/packages.x86_64 +++ b/iso/packages.x86_64 @@ -4,6 +4,21 @@ base-devel linux linux-firmware linux-headers +# CPU microcode — applied early by GRUB on the installed system (picked up by +# the bootloader module). amd-ucode for the dev laptop's Ryzen; intel-ucode for +# Intel targets. bos-copy-kernel also stages these into the live target /boot. +amd-ucode +intel-ucode + +# Power management (vendor-neutral; works on Intel and AMD). tlp auto-tunes power +# by AC/battery and CPU driver; hypridle/hyprlock handle idle dim/off/lock/suspend +# and the lock screen; upower exposes battery state. NOT power-profiles-daemon — +# it conflicts with tlp. +tlp +tlp-rdw +upower +hypridle +hyprlock # Bootloader + filesystem grub @@ -37,6 +52,10 @@ inotify-tools # Wayland / Hyprland hyprland xdg-desktop-portal-hyprland +# Login manager for the installed system (Wayland-native; enabled by +# post-install.sh, launches the Hyprland session via tuigreet → bos-session). +greetd +greetd-tuigreet xdg-utils xdg-user-dirs polkit @@ -53,7 +72,8 @@ pipewire-jack networkmanager network-manager-applet iw -iwd +# Wi-Fi backend for NetworkManager (its default; no extra config needed). +wpa_supplicant bluez bluez-utils @@ -73,29 +93,47 @@ noto-fonts-emoji ttf-jetbrains-mono # Terminal -foot +kitty # File manager nautilus +# Web browser (served from the [Breadway] repo; AUR zen-browser-bin republished +# there so the ISO build can pull it via pacman). mailcap satisfies zen's +# mime-types dependency explicitly. +zen-browser-bin +mailcap # Installer — Calamares is AUR-only; built in-house and served from [breadway] # (calamares 3.4.x is already Qt6; there is no separate calamares-qt6 package) calamares -# Bread ecosystem — sourced from [breadway] repo. Baked into the squashfs so a -# fresh install (and the live session) has the full desktop with no network. -bakery -bread -breadbar -breadbox -breadcrumbs -breadpad +# Bread ecosystem. +# +# The bread apps themselves (bakery, bread, breadbar, breadbox, breadcrumbs, +# breadpad) are NOT pacman packages here — they are bakery-managed binaries +# baked into /etc/skel/.local/bin at build time (see build-local.sh), so every +# user gets the exact versions from this laptop's bakery install with no +# network/DNS needed at install or runtime. Their runtime system deps are pulled +# in elsewhere in this list (gtk4, gtk4-layer-shell, iw, libpulse, librsvg, +# networkmanager, openssl, zlib, systemd-libs) — keep those even though no bread +# package depends on them. +# +# bos-settings is a BOS-specific pacman package (not part of the bakery index), +# so it stays here, served from the [breadway] repo. bos-settings # Input / screen utilities brightnessctl grim slurp +# Clipboard (Wayland copy/paste; also clipboard screenshots) and media keys. +wl-clipboard +playerctl +# Wallpaper daemon + pywal (drives the bread* colour palette from the wallpaper). +awww +python-pywal +# Boot splash (BOS logo + spinner instead of kernel text). +plymouth # Utilities sudo @@ -110,5 +148,32 @@ man-db man-pages less +# Base CLI tools every install should have. +# Editors +nano +micro +vim +# Shell UX +bash-completion +# System / hardware inspection +htop +usbutils +pciutils +dmidecode +lsof +tree +fastfetch +# Removable-media filesystems (USB sticks, external drives) +ntfs-3g +exfatprogs +# Archives +7zip +zip +unrar +# Remote access (ssh client; sshd ships disabled) +openssh +# Mirror management (refresh /etc/pacman.d/mirrorlist for the user's location) +reflector + # Dev tools (for bos-settings standalone install) rustup diff --git a/iso/profiledef.sh b/iso/profiledef.sh index 6c496b2..ed30e07 100644 --- a/iso/profiledef.sh +++ b/iso/profiledef.sh @@ -20,4 +20,5 @@ 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-session"]="0:0:755" )