Compare commits

..

10 commits
stable ... main

Author SHA1 Message Date
2b8eb2f1b6 Merge pull request 'skel/hypr: GSK_RENDERER=cairo + wire up shell layer rules' (#7) from fix/gsk-renderer-black-surfaces into main 2026-08-31 20:28:01 +08:00
Breadway
232ee0c7a9 skel/hypr: GSK_RENDERER=cairo + wire up the shell layer rules
Two fixes for the black-shell / no-blur symptoms (seen first in a VM,
but the renderer one bites real hardware too):

1. `GSK_RENDERER=cairo` in the session env. GTK4's default renderer
   (ngl/vulkan on 4.14+) draws transparent layer-shell surfaces as
   opaque black on wlroots — the whole bread shell (breadbox launcher,
   breadclip popup, breadbar, breadhelp, bos-settings) goes black-on-
   black, and it's guaranteed under a VM's virtio-gpu where there's no
   real GL/Vulkan context. cairo (software) always composites
   transparency correctly; the shell is flat colour/text/icons so
   there's no visible cost, and idle memory drops (~40%, no Mesa
   driver resident). Override with GSK_RENDERER=gl in the session for a
   GPU-heavy GTK4 app.

2. `scripts/ui/rules.lua` (new) + a `pcall(dofile ...)` for it in
   hyprland.lua + `bread-theme layerrules` in the bootstrap. The
   shell-theme work added layer-rule *generation* (bread-theme writes
   ~/.config/hypr/layerrules.json from the theme's [compositor] table)
   but BOS never shipped the read side, so no blur / ignore-alpha /
   per-namespace motion was ever applied to breadbar / breadbox /
   breadclip. rules.lua reads the JSON and emits `hl.layer_rule`,
   with the pre-theme hardcoded rule set as a pcall-guarded fallback
   (matches the live reference config). Layer rules only — window /
   workspace / focus rules stay in hyprland.lua.
2026-08-31 20:27:41 +08:00
87887f09af Merge pull request 'ISO: flip [breadway] to the signed dl.breadway.dev/arch repo' (#5) from fix/breadway-repo-signing into main
Reviewed-on: #5
2026-08-31 19:18:50 +08:00
e3cdf33f83 Merge pull request 'packaging: republish python-pywal to [breadway]' (#6) from fix/republish-python-pywal into main
Some checks failed
Build and publish python-pywal / python-pywal (push) Failing after 47s
Reviewed-on: #6
2026-08-31 19:18:37 +08:00
Breadway
716c77f93b packaging: republish python-pywal to [breadway]
All checks were successful
Build and publish python-pywal / python-pywal (push) Successful in 1m21s
python-pywal was dropped from Arch's [extra] repo (AUR-only now), so
`pacstrap` can no longer resolve it and every ISO build fails with
"target not found: python-pywal". The `wal` binary is load-bearing —
bread-theme shells out to it to extract the colour palette from the
user's wallpaper.

Republish it the same way as calamares / bibata / powerlevel10k /
yay-bin:

- `packaging/python-pywal/PKGBUILD` — in-house copy of the AUR PKGBUILD
  (Morten Linderud's), modernised to `python -m build` / `installer`
  instead of the removed `setup.py install`, sha256-only sources like
  the sibling PKGBUILDs. Test-built locally: 28 unit tests pass, package
  ships `/usr/bin/wal`.
- `.forgejo/workflows/python-pywal.yml` — builds + PUTs to the Forgejo
  Arch registry on a push to `packaging/python-pywal/**`.
- `signed-repo.yml` gains it as a `workflow_run` trigger;
  `ci-publish-signed-repo.sh` gains it in `PACKAGES` so the signed
  dl.breadway.dev/arch db picks it up.
- packages.x86_64 keeps the `python-pywal` line (now sourced from
  [breadway]) with a note.

Ordering: `python-pywal.yml` must publish to the registry once before
`signed-repo.yml` runs, or the collect step errors "registry db missing
packages: python-pywal".
2026-08-31 19:15:38 +08:00
Breadway
98cfe9d60b ISO: flip [breadway] to the signed dl.breadway.dev/arch repo
The signed repo is live: https://dl.breadway.dev/arch/x86_64/ serves
breadway.db + .files + every .pkg.tar.zst with a detached .sig from the
BOS release key (56203B86A110695AE7F310934AF3323D678EB5E2 = KEYS.asc),
rebuilt from the Forgejo Arch registry by signed-repo.yml +
scripts/ci-publish-signed-repo.sh. Verified: db/pkg sigs all GPG-good
against KEYS.asc.

Executes the "After the signed repo exists" plan in docs/signed-repo.md:

- `iso/pacman.conf` + `iso/airootfs/etc/pacman.conf`: section renamed
  `[Breadway.os.git.breadway.dev]` → `[breadway]` (matches breadway.db),
  `Server = https://dl.breadway.dev/arch/$arch`, `SigLevel = Required`.
  The old "Forgejo has no db sigs / KEYS.asc is not a repo key / do NOT
  flip" comments are gone — both are now false.
- `iso/airootfs/etc/pacman.d/breadway-repo.asc`: the public key, baked
  into the image.
- `build-local.sh`: trust the key in the build host's pacman keyring
  before mkarchiso (so pacstrap can verify [breadway] while assembling
  the airootfs); drop the now-obsolete Forgejo-registry URL rewrite.
- `iso/airootfs/root/customize_airootfs.sh` (new): trust the key in the
  image keyring so the live medium — and, via calamares unpackfs, the
  installed target — verify [breadway]. (archiso warns this hook is
  deprecated; there is no replacement for "add a repo key to the image
  keyring" and BOS ships no pacman-init.service.)
- `calamares/post-install.sh`: `pacman-key --add` + `--lsign-key` the
  BOS key in the target chroot as a fallback (unpackfs can skip
  /etc/pacman.d/gnupg).
- README.md / DESIGN.md / docs/signed-repo.md updated.

NOT yet done: build the ISO (`sudo ./build-local.sh`) and VM-verify
`pacman -Sy` + a `[breadway]` install with no signature prompt, on both
the live medium and a fresh install. The build-time keyring path
(pacstrap -G vs host keyring vs customize_airootfs) may need a tweak
once the real build runs.
2026-08-31 18:22:55 +08:00
Breadway
b38bbbac1a iso: add external-monitors bread module for zero-config docking 2026-08-23 15:23:07 +08:00
Breadway
df2e1310bb iso: audit-sweep UID, bakery update, lockfile, welcome, autostart
Resolve MAIN_USER after deleting liveuser so Snapper and skel target the
installed account. Wrap bakery update with sudo -n/pkexec for /usr/local.
Pin bakery and bread-theme 0.7.4; require bread-emit and bread-module-host.
Make Calamares internet check non-blocking against breadway.dev. Autostart
breadlock listen. Smoke avahi-daemon.socket to match post-install.
2026-08-23 14:32:30 +08:00
Breadway
b270d64adc ci: sign [breadway] repo as the runner user
Docker as root left 0600 .sig files the runner could not chmod
(run 1050), so publish_tree never moved the tree into
/srv/breadway-dl/arch/x86_64. Sign as the host uid so nginx can
read the files and the next publish can replace them.
2026-08-16 08:07:53 +08:00
Breadway
64aa3611b4 ci: do not fail signed-repo publish on chmod of gpg signatures
repo-add -s already wrote the signed db; chmod a+r on .sig files
returned EPERM and rolled back the stage dir.
2026-08-16 05:35:56 +08:00
24 changed files with 673 additions and 104 deletions

View file

@ -0,0 +1,39 @@
name: Build and publish python-pywal
# python-pywal was dropped from Arch's [extra] repo (AUR-only now), but the ISO
# needs the `wal` binary (bread-theme extracts the wallpaper palette with it).
# BOS keeps an in-house PKGBUILD and publishes to the [breadway] repo — same
# pattern as calamares / bibata / powerlevel10k / yay-bin.
on:
push:
paths:
- 'packaging/python-pywal/**'
workflow_dispatch:
jobs:
python-pywal:
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 \
python python-build python-installer python-wheel python-setuptools imagemagick
useradd -m builder
git config --global --add safe.directory '*'
# Clone the ref that triggered this run (not the default branch) —
# same as the other packaging workflows.
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/python-pywal && makepkg -f --noconfirm"
PKG=$(find /home/builder/src/packaging/python-pywal -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

@ -21,6 +21,7 @@ on:
- Build and publish bibata-cursor-theme - Build and publish bibata-cursor-theme
- Build and publish powerlevel10k - Build and publish powerlevel10k
- Build and publish yay-bin - Build and publish yay-bin
- Build and publish python-pywal
types: [completed] types: [completed]
concurrency: concurrency:

View file

@ -16,7 +16,7 @@ taken as current:
| Work on `dev`; origin = GitHub | Single-trunk `main`; `stable` is a CI marker. `origin` = Forgejo, `github` = GitHub. | | Work on `dev`; origin = GitHub | Single-trunk `main`; `stable` is a CI marker. `origin` = Forgejo, `github` = GitHub. |
| `[breadway]` provides bakery/breadbar/bos-settings | `[breadway]` is breadlock + AUR republishes. Desktop apps are bakery. **Not shipped:** breadcast, breadarr. | | `[breadway]` provides bakery/breadbar/bos-settings | `[breadway]` is breadlock + AUR republishes. Desktop apps are bakery. **Not shipped:** breadcast, breadarr. |
| NVIDIA / A/B / Secure Boot / LUKS2 | NVIDIA proprietary is **unsupported**. A/B root swapping is **not implemented**. Secure Boot is **Setup Mode only** (self-signed `sbctl`). Disk encryption is **LUKS1** because GRUB cannot unlock LUKS2+Argon2id. | | NVIDIA / A/B / Secure Boot / LUKS2 | NVIDIA proprietary is **unsupported**. A/B root swapping is **not implemented**. Secure Boot is **Setup Mode only** (self-signed `sbctl`). Disk encryption is **LUKS1** because GRUB cannot unlock LUKS2+Argon2id. |
| `SigLevel = Required` on `[breadway]` | **No.** Forgejo's Arch registry has no pacman-compatible db signatures. `SigLevel = Never` is TLS only; flipping Required without a signed db breaks installs. `KEYS.asc` signs ISO SHA256SUMS, not the pacman repo. | | `SigLevel = Required` on `[breadway]` | **Yes, as of the signed repo.** `[breadway]` points at `https://dl.breadway.dev/arch` where `scripts/ci-publish-signed-repo.sh` detach-signs every `.pkg.tar.zst` and the db with the BOS release key (`56203B86…`, `KEYS.asc`). That key is trusted in the pacman keyring at build time, on the live medium, and on the installed target. |
--- ---

View file

@ -174,10 +174,10 @@ dedicated release-signing key (not reused from anything else):
5620 3B86 A110 695A E7F3 1093 4AF3 323D 678E B5E2 5620 3B86 A110 695A E7F3 1093 4AF3 323D 678E B5E2
``` ```
The public half is committed at [`KEYS.asc`](KEYS.asc). That key signs The public half is committed at [`KEYS.asc`](KEYS.asc). The same key signs
**ISO checksums only** — it does not sign the `[breadway]` pacman repo the ISO checksums **and** the `[breadway]` pacman repo — every package and
(Forgejo's Arch registry has no pacman-compatible db signatures; that the db at `https://dl.breadway.dev/arch` carry a `.sig` from it, and that
section stays `SigLevel = Never` until a signed repo exists — see section is `SigLevel = Required` (see
[docs/signed-repo.md](docs/signed-repo.md)). To verify a download: [docs/signed-repo.md](docs/signed-repo.md)). To verify a download:
```sh ```sh
@ -381,8 +381,9 @@ until `dl.breadway.dev/arch` exists).
- **Snapshots assume btrfs**: the snapper/grub-btrfs tooling expects the default - **Snapshots assume btrfs**: the snapper/grub-btrfs tooling expects the default
btrfs subvolume layout the installer creates. Recovery is the GRUB btrfs subvolume layout the installer creates. Recovery is the GRUB
snapshots submenu, not `snapper rollback` — [docs/hardware.md](docs/hardware.md). snapshots submenu, not `snapper rollback` — [docs/hardware.md](docs/hardware.md).
- **`[breadway]` signatures**: `SigLevel = Never` until a signed repo is - **`[breadway]` signatures**: `SigLevel = Required` — the signed repo at
stood up at `dl.breadway.dev/arch`. See [docs/signed-repo.md](docs/signed-repo.md). `dl.breadway.dev/arch` is live (db + every package `.sig`ned with the BOS
release key). See [docs/signed-repo.md](docs/signed-repo.md).
## Recovery ## Recovery

View file

@ -25,14 +25,19 @@ OUT="${OUT:-$REPO/out}"
STAGE=/tmp/bos-iso-stage STAGE=/tmp/bos-iso-stage
rm -rf "$STAGE" && cp -a "$REPO/iso" "$STAGE" rm -rf "$STAGE" && cp -a "$REPO/iso" "$STAGE"
# Rewrite the [breadway] pacman repo URL to the fastest reachable address. # [breadway] now points at the signed public repo https://dl.breadway.dev/arch
# CI_BUILD=1 — container runs on hestia with --network=host; localhost:3002 is direct # (SigLevel = Required) — no Forgejo-registry URL rewrite needed anymore.
# default — building on hermes; git.breadway.dev is flaky from there, use Tailscale #
# Only ever rewrites the staged copy, never the committed pacman.conf. # Trust the [breadway] repo key in *this* build host's pacman keyring so
if [ "${CI_BUILD:-0}" = "1" ]; then # `pacstrap` can verify [breadway] packages while assembling the airootfs.
sed -i 's#https://git.breadway.dev/api/packages/Breadway/arch/os#http://localhost:3002/api/packages/Breadway/arch/os#' "$STAGE/pacman.conf" # The same key is baked into the image at etc/pacman.d/breadway-repo.asc and
else # re-trusted on the live medium / installed target (calamares/post-install.sh).
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" BREADWAY_KEY_FPR="56203B86A110695AE7F310934AF3323D678EB5E2"
BREADWAY_KEY_SRC="$REPO/iso/airootfs/etc/pacman.d/breadway-repo.asc"
if ! pacman-key --list-keys "$BREADWAY_KEY_FPR" &>/dev/null; then
echo "=== trusting [breadway] repo key ($BREADWAY_KEY_FPR) in the host pacman keyring ==="
pacman-key --add "$BREADWAY_KEY_SRC"
pacman-key --lsign-key "$BREADWAY_KEY_FPR"
fi fi
if [ "${FAST_BUILD:-0}" = "1" ]; then if [ "${FAST_BUILD:-0}" = "1" ]; then

View file

@ -1,18 +1,20 @@
# Signed `[breadway]` repo # Signed `[breadway]` repo
Today the ISO's `[Breadway.os.git.breadway.dev]` section is **Status: live.** The ISO's `[breadway]` section is `SigLevel = Required`
`SigLevel = Never`. That is TLS-only integrity: packages come from Forgejo's and points at `https://dl.breadway.dev/arch/$arch`, where every
Arch registry, which does **not** serve pacman-compatible database `.pkg.tar.zst` and the db carry a detached `.sig` from the BOS release key
signatures. `KEYS.asc` signs **ISO `SHA256SUMS`** and, once published, the (`56203B86…`, `KEYS.asc`, `releases@breadway.dev`). That key is trusted in
`dl.breadway.dev/arch` database. It is **not** imported as a pacman repo key the pacman keyring at build time (`build-local.sh`), on the live medium
on the ISO yet. Do not flip `SigLevel` to `Required` on that section until (`iso/airootfs/root/customize_airootfs.sh`), and on the installed target
a signed repo exists and has been verified; Required without signatures (`iso/airootfs/etc/calamares/post-install.sh`).
breaks the ISO and every installed system.
The signed repo belongs at `https://dl.breadway.dev/arch`, not on Forgejo's Forgejo publishing is unchanged: `package.yml` / packaging workflows still
registry. Forgejo publishing stays as it is (`package.yml` / packaging PUT unsigned `.pkg.tar.zst` to Forgejo's Arch registry. The signed tree at
workflows PUT unsigned `.pkg.tar.zst` so existing Never installs keep `dl.breadway.dev/arch` is rebuilt from that registry by
working). `.forgejo/workflows/signed-repo.yml` + `scripts/ci-publish-signed-repo.sh`.
The rest of this doc is the original stand-up / verification procedure,
kept for reference and for re-verifying after key rotation.
## Stand up `dl.breadway.dev/arch` ## Stand up `dl.breadway.dev/arch`
@ -47,7 +49,7 @@ On disk: `/srv/breadway-dl/arch/x86_64/` (nginx already serves
The job collects the current ISO `[breadway]` set from the Forgejo Arch The job collects the current ISO `[breadway]` set from the Forgejo Arch
registry (breadlock + calamares, zen-browser-bin, bibata-cursor-theme-bin, registry (breadlock + calamares, zen-browser-bin, bibata-cursor-theme-bin,
zsh-theme-powerlevel10k, yay-bin). Leftover bakery-channel pacman packages zsh-theme-powerlevel10k, yay-bin, python-pywal). Leftover bakery-channel pacman packages
still sitting in that registry are **not** copied. Optional still sitting in that registry are **not** copied. Optional
`BREADWAY_PKG_DIR` on the runner overrides individual files. `BREADWAY_PKG_DIR` on the runner overrides individual files.
@ -74,7 +76,7 @@ Pacman fetches `<section>.db` + `<section>.db.sig` from `Server`.
## Dispatch the workflow ## Dispatch the workflow
Forgejo UI: **Actions → "Publish signed [breadway] repo" → Run workflow**. Forgejo UI: **Actions → "Publish signed [breadway] repo" → Run workflow**.
Select this branch (`feature/signed-repo`) until it is on `main`. Select `main`.
API (`workflow_dispatch`): API (`workflow_dispatch`):
@ -83,13 +85,11 @@ curl -fsS -X POST \
-H "Authorization: token ${RELEASE_TOKEN}" \ -H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
"https://git.breadway.dev/api/v1/repos/Breadway/bos/actions/workflows/signed-repo.yml/dispatches" \ "https://git.breadway.dev/api/v1/repos/Breadway/bos/actions/workflows/signed-repo.yml/dispatches" \
-d '{"ref":"feature/signed-repo"}' -d '{"ref":"main"}'
``` ```
After merge, use `"ref":"main"`.
It also runs after the in-repo AUR republish workflows complete It also runs after the in-repo AUR republish workflows complete
(`calamares` / `bibata` / `powerlevel10k` / `yay-bin`). breadlock lives in (`calamares` / `bibata` / `powerlevel10k` / `yay-bin` / `python-pywal`). breadlock lives in
another repo; that job can fire this one with `repository_dispatch` event another repo; that job can fire this one with `repository_dispatch` event
`publish-signed-repo` (or dispatch from the UI after a breadlock tag). `publish-signed-repo` (or dispatch from the UI after a breadlock tag).
@ -141,15 +141,29 @@ stays; Never installs keep working. The signed tree is rebuilt by the bos
workflow above (registry fetch + sign + `repo-add -s`), not by writing workflow above (registry fetch + sign + `repo-add -s`), not by writing
`/srv` from breadlock's container. `/srv` from breadlock's container.
## After the signed repo exists ## The ISO flip (done)
Only after `https://dl.breadway.dev/arch/x86_64/breadway.db.sig` HEADs 200 All three steps have landed:
and the verify commands above succeed:
1. Import `KEYS.asc` into the ISO keyring (`pacman-key --add` + `--lsign-key`). 1. **Key trusted.** The public key is committed at
2. Point `[breadway]` `Server` at `https://dl.breadway.dev/arch/$arch`. `iso/airootfs/etc/pacman.d/breadway-repo.asc`. `build-local.sh`
3. Only then flip that section to `SigLevel = Required`. `pacman-key --add` + `--lsign-key`s it into the build host keyring;
`customize_airootfs.sh` does the same in the airootfs;
`calamares/post-install.sh` re-does it in the target chroot.
2. **`Server`** in `iso/pacman.conf` and `iso/airootfs/etc/pacman.conf`
points at `https://dl.breadway.dev/arch/$arch`, section renamed to
`[breadway]` (matching `breadway.db`).
3. **`SigLevel = Required`** on that section.
Do not do those three steps against Forgejo's registry. See ### Re-verify after any build
`iso/pacman.conf` and `iso/airootfs/etc/pacman.conf`. This branch does
**not** change either file. In a VM booted from a fresh ISO:
```sh
sudo pacman -Sy # must fetch breadway.db + .sig, no signature error
sudo pacman -Si breadlock # lists the [breadway] section
sudo pacman -S --noconfirm yay-bin # installs with no key prompt
```
Then run the installer and, on the installed system, `sudo pacman -Sy`
again — the target keyring must already trust `56203B86…`.

View file

@ -3,9 +3,19 @@ showSupportUrl: false
showKnownIssuesUrl: false showKnownIssuesUrl: false
showReleaseNotesUrl: false showReleaseNotesUrl: false
# 3.4.2 schema: `check` is shown; only `required` blocks Next. Internet is
# informational so offline installs proceed. Do not probe archlinux.org.
requirements: requirements:
requiredStorage: 20 requiredStorage: 20
requiredRam: 2.0 requiredRam: 2.0
checkInternet: true internetCheckUrl: "https://breadway.dev"
checkPower: true check:
internetCheckUrl: "https://archlinux.org" - storage
- ram
- power
- internet
- root
required:
- storage
- ram
- root

View file

@ -8,8 +8,6 @@
# Best-effort: do NOT use `set -e`; a single failure here must not abort the rest. # Best-effort: do NOT use `set -e`; a single failure here must not abort the rest.
set -uo pipefail set -uo pipefail
MAIN_USER="$(getent passwd 1000 | cut -d: -f1 || true)"
# Whether Calamares encrypted the root partition (LUKS) — checked once here, # Whether Calamares encrypted the root partition (LUKS) — checked once here,
# used below to conditionally wire mkinitcpio's encrypt hook and GRUB's # used below to conditionally wire mkinitcpio's encrypt hook and GRUB's
# cryptodisk support. `lsblk TYPE` reports "crypt" for a cryptsetup-opened # cryptodisk support. `lsblk TYPE` reports "crypt" for a cryptsetup-opened
@ -33,6 +31,14 @@ rm -f /usr/local/bin/bos-live-setup /usr/local/bin/bos-launch-calamares
rm -f /etc/sudoers.d/99-bos-live rm -f /etc/sudoers.d/99-bos-live
userdel -r liveuser 2>/dev/null || true userdel -r liveuser 2>/dev/null || true
# Live ISO creates liveuser as UID 1000; Calamares then creates the real
# account as 1001. Capture AFTER userdel so Snapper ALLOW_USERS and skel
# copy the installed user, not the deleted live account.
MAIN_USER="$(getent passwd 1000 | cut -d: -f1 || true)"
if [[ -z "$MAIN_USER" || "$MAIN_USER" == "liveuser" ]]; then
MAIN_USER="$(getent passwd | awk -F: '$3 >= 1000 && $3 < 60000 && $1 != "liveuser" { print $1; exit }')"
fi
# unpackfs copies the entire live squashfs onto the target. Remove live-only # unpackfs copies the entire live squashfs onto the target. Remove live-only
# packages (Calamares + archiso boot chain + memtest/EFI-shell payloads) so # packages (Calamares + archiso boot chain + memtest/EFI-shell payloads) so
# they do not stay on disk forever. pacman -Rs (not -Rns) keeps /etc configs # they do not stay on disk forever. pacman -Rs (not -Rns) keeps /etc configs
@ -67,15 +73,23 @@ passwd -l root || true
# over to the target (unpackfs may skip it / perms differ), leaving the installed # 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 # 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 # 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 # fresh install can update out of the box. archlinux-keyring verifies official
# and is the only keyring populated — it verifies official Arch packages. # Arch packages; the BOS release key (56203B86…, shipped at
# [breadway] stays SigLevel=Never (Forgejo does not serve pacman-compatible # /etc/pacman.d/breadway-repo.asc) verifies the signed [breadway] repo at
# db signatures). Do not import KEYS.asc here: that key signs ISO SHA256SUMS, # dl.breadway.dev/arch — SigLevel = Required there, every package and the db
# not the pacman repo; treating it as a repo key would be a lie. # carry a .sig from it.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
BREADWAY_KEY_FPR="56203B86A110695AE7F310934AF3323D678EB5E2"
if command -v pacman-key &>/dev/null; then if command -v pacman-key &>/dev/null; then
pacman-key --init || echo "WARN: pacman-key --init failed" pacman-key --init || echo "WARN: pacman-key --init failed"
pacman-key --populate archlinux || echo "WARN: pacman-key --populate failed" pacman-key --populate archlinux || echo "WARN: pacman-key --populate failed"
if [[ -f /etc/pacman.d/breadway-repo.asc ]]; then
pacman-key --add /etc/pacman.d/breadway-repo.asc \
&& pacman-key --lsign-key "$BREADWAY_KEY_FPR" \
|| echo "WARN: could not trust the [breadway] repo key — pacman -Sy will fail on [breadway]"
else
echo "WARN: /etc/pacman.d/breadway-repo.asc missing — [breadway] (SigLevel=Required) will not verify"
fi
fi fi
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View file

@ -32,18 +32,17 @@ Include = /etc/pacman.d/mirrorlist
# are NOT here; they are bakery-baked into /usr/local at ISO build time. # are NOT here; they are bakery-baked into /usr/local at ISO build time.
# #
# Packages are published to the Forgejo Arch registry (group "os") by the # Packages are published to the Forgejo Arch registry (group "os") by the
# .forgejo/workflows/*.yml workflows in this repo (and breadlock's). # .forgejo/workflows/*.yml workflows; scripts/ci-publish-signed-repo.sh then
# collects them, detach-signs each .pkg.tar.zst with the BOS release key
# (releases@breadway.dev), runs `repo-add -s`, and publishes the signed db
# at https://dl.breadway.dev/arch/$arch (signed-repo.yml).
# #
# Forgejo's Arch package registry does not serve pacman-compatible db # SigLevel = Required: every package AND the db carry a .sig from key
# signatures. SigLevel = Never is TLS-only integrity: the connection is # 56203B86A110695AE7F310934AF3323D678EB5E2 — the same key committed as
# HTTPS (or rewritten to hestia's localhost:3002 in CI). breadlock (PAM) # KEYS.asc / etc/pacman.d/breadway-repo.asc, imported into the pacman
# rides this repo. Do NOT flip to SigLevel = Required unless a signed db # keyring at build time (build-local.sh), on the live medium, and on the
# has been verified to work — Required without signatures breaks the ISO # installed target (calamares/post-install.sh).
# and every install that uses [breadway]. KEYS.asc is the ISO SHA256SUMS
# signing key, not a pacman repo key.
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# The section name must match Forgejo's served db filename [breadway]
# ({owner}.{group}.{domain}.db) — pacman fetches "<section>.db" from Server. SigLevel = Required
[Breadway.os.git.breadway.dev] Server = https://dl.breadway.dev/arch/$arch
SigLevel = Never
Server = https://git.breadway.dev/api/packages/Breadway/arch/os/$arch

View file

@ -0,0 +1,15 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEakhwGhYJKwYBBAHaRw8BAQdA/sZ/GYec5M2MD+w20mVF5tMUhGji210Dg7zL
TAhNsg60WUJPUyBSZWxlYXNlIFNpZ25pbmcgKGdpdC5icmVhZHdheS5kZXYvQnJl
YWR3YXkvYm9zIHJlbGVhc2VzIG9ubHkpIDxyZWxlYXNlc0BicmVhZHdheS5kZXY+
iJYEExYKAD4WIQRWIDuGoRBpWufzEJNK8zI9Z4614gUCakhwGgIbIwUJA8JnAAUL
CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRBK8zI9Z4614ggYAQDP8FTZ14i9YPKD
ARvZuP5QaYOUFhQ8uyG0CowXKy9O0AEAqYfjnvyJI3N651pVFSNUXyP16w1kMPSs
K0g3CLsztQ+4OARqSHAaEgorBgEEAZdVAQUBAQdAuJFuy2GHz5m9wXTm/PdSpLE9
gERwHOLyM1OFuttrJW4DAQgHiH4EGBYKACYWIQRWIDuGoRBpWufzEJNK8zI9Z461
4gUCakhwGgIbDAUJA8JnAAAKCRBK8zI9Z4614nzLAP9grcIFsAAeCyVKhziHmpXq
E0Hm6FfIr4sdEf63HZkyfwD/XeKeWfb3EWvVsloJrZZ9tDmR67iK52Hwl82wfFAU
cAo=
=Mrh1
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -0,0 +1,228 @@
-- external-monitors — behave like a normal laptop desktop
--
-- Plug in any display (HDMI, DisplayPort, USB-C dock, a random TV) and
-- the session just works. No output names to edit.
--
-- • the laptop panel stays at its preferred (native) mode
-- • each external uses its preferred mode and refresh
-- • new screens clone the laptop (set ARRANGE = "extend" to sit to the right)
-- • closing the lid does not sleep while an external is on
-- • unplug everything and the laptop is the only display again
--
-- Drop-in: copy to ~/.config/bread/modules/ and `bread reload`.
local M = bread.module({
name = "external-monitors",
version = "1.0.0",
after = { "bread.monitors" },
})
-- "mirror" = every external clones the laptop (presentations, TVs)
-- "extend" = extra desktop to the right
local ARRANGE = "mirror"
local SCALE = "auto"
local INTERNAL_RE = "^eDP"
local INHIBITOR = "/tmp/bread-lid-inhibitor.pid"
local function inhibit_lid()
if bread.fs.exists(INHIBITOR) then return end
bread.exec(
"bash -c 'systemd-inhibit --what=handle-lid-switch --who=bread "
.. "--why=external-display sleep infinity & echo $! > "
.. INHIBITOR
.. "'"
)
end
local function release_lid()
bread.exec(
"bash -c 'kill $(cat " .. INHIBITOR .. " 2>/dev/null) 2>/dev/null; rm -f " .. INHIBITOR .. "'"
)
end
local function is_internal(name)
return type(name) == "string" and name:match(INTERNAL_RE) ~= nil
end
local function drm_status(name)
for card = 0, 5 do
local raw = bread.fs.read(string.format("/sys/class/drm/card%d-%s/status", card, name))
if raw then
return raw:match("^%s*(%S+)")
end
end
return nil
end
local function drm_first_mode(name)
for card = 0, 5 do
local raw = bread.fs.read(string.format("/sys/class/drm/card%d-%s/modes", card, name))
if raw then
local w, h = raw:match("(%d+)x(%d+)")
if w then
return tonumber(w), tonumber(h)
end
end
end
return 1920, 1080
end
local function list_connectors()
local names = {}
local ok, out = bread.exec_capture("ls /sys/class/drm", { timeout_ms = 500 })
if not ok or not out then
return names
end
for ent in out:gmatch("[^%s]+") do
local name = ent:match("^card%d+%-(.+)$")
if name and not name:match("^Writeback") then
names[#names + 1] = name
end
end
table.sort(names)
return names
end
local function connected()
local internal, externals = nil, {}
for _, name in ipairs(list_connectors()) do
if drm_status(name) == "connected" then
if is_internal(name) then
internal = internal or name
else
externals[#externals + 1] = name
end
end
end
return internal or "eDP-1", externals
end
-- BOS Hyprland talks Lua (`hl.monitor`). Stock Hyprland uses the
-- `monitor=` keyword. Try eval first, then keyword.
local function apply_monitor(opts)
local extra = ""
if opts.mirror and opts.mirror ~= "" then
extra = string.format(", mirror = %q", opts.mirror)
end
local expr = string.format(
"hl.monitor({ output = %q, mode = %q, position = %q, scale = %q%s })",
opts.output,
opts.mode or "preferred",
opts.position or "0x0",
opts.scale or SCALE,
extra
)
local resp = bread.hyprland.eval(expr)
if type(resp) == "string" and resp:match("error") then
local spec = string.format(
"%s, %s, %s, %s",
opts.output,
opts.mode or "preferred",
opts.position or "0x0",
opts.scale or SCALE
)
if opts.mirror and opts.mirror ~= "" then
spec = spec .. ", mirror, " .. opts.mirror
end
bread.hyprland.keyword("monitor", spec)
end
end
local function apply(internal, externals)
apply_monitor({
output = internal,
mode = "preferred",
position = "0x0",
scale = SCALE,
})
if ARRANGE == "mirror" then
for _, name in ipairs(externals) do
apply_monitor({
output = name,
mode = "preferred",
position = "0x0",
scale = SCALE,
mirror = internal,
})
end
return
end
local x = select(1, drm_first_mode(internal)) or 1920
for _, name in ipairs(externals) do
apply_monitor({
output = name,
mode = "preferred",
position = x .. "x0",
scale = SCALE,
})
local w = select(1, drm_first_mode(name)) or 1920
x = x + w
end
end
function M.on_load()
local last = nil
local applied = false
local function evaluate()
local internal, externals = connected()
local sig = internal .. "|" .. table.concat(externals, ",")
if sig == last then
return
end
last = sig
if #externals == 0 then
if applied then
apply_monitor({
output = internal,
mode = "preferred",
position = "0x0",
scale = SCALE,
})
release_lid()
applied = false
end
return
end
apply(internal, externals)
inhibit_lid()
applied = true
bread.log("[external-monitors] " .. internal .. " + " .. table.concat(externals, ", "))
end
local settle = bread.debounce(1500, evaluate)
bread.on("bread.hyprland.monitor.connected", function(event)
local name = event.data and event.data.name
if name and not is_internal(name) then
bread.notify("Display connected: " .. name, { urgency = "low" })
end
settle()
end)
bread.on("bread.hyprland.monitor.disconnected", function()
settle()
end)
bread.on("bread.device.**", function(event)
local sub = event.data and event.data.subsystem
if sub == "drm" then
settle()
end
end)
bread.hyprland.on_raw("configreloaded", function()
last = nil
evaluate()
end)
bread.every(3000, evaluate)
settle()
end
return M

View file

@ -7,6 +7,7 @@
{ "command": "breadhelp --autostart", "label": "BOS Help (first-run onboarding)", "enabled": true }, { "command": "breadhelp --autostart", "label": "BOS Help (first-run onboarding)", "enabled": true },
{ "command": "bash -c 'command -v breadpaper >/dev/null && exec breadpaper listen'", "label": "Wallpaper command bus (breadpaper listen)", "enabled": true }, { "command": "bash -c 'command -v breadpaper >/dev/null && exec breadpaper listen'", "label": "Wallpaper command bus (breadpaper listen)", "enabled": true },
{ "command": "bash -c 'command -v breadshot >/dev/null && exec breadshot listen'", "label": "Screenshot command bus (breadshot listen)", "enabled": true }, { "command": "bash -c 'command -v breadshot >/dev/null && exec breadshot listen'", "label": "Screenshot command bus (breadshot listen)", "enabled": true },
{ "command": "bash -c 'command -v breadlock >/dev/null && exec breadlock listen'", "label": "Lock command bus (breadlock listen)", "enabled": true },
{ "command": "bash -c 'command -v breadbox >/dev/null && exec breadbox listen'", "label": "Launcher command bus (breadbox listen)", "enabled": true }, { "command": "bash -c 'command -v breadbox >/dev/null && exec breadbox listen'", "label": "Launcher command bus (breadbox listen)", "enabled": true },
{ "command": "bash -c 'command -v breadhelp >/dev/null && exec breadhelp listen'", "label": "Help command bus (breadhelp listen)", "enabled": true }, { "command": "bash -c 'command -v breadhelp >/dev/null && exec breadhelp listen'", "label": "Help command bus (breadhelp listen)", "enabled": true },
{ "command": "bash -c 'command -v breadsearch >/dev/null && exec breadsearch listen'", "label": "Search command bus (breadsearch listen)", "enabled": true }, { "command": "bash -c 'command -v breadsearch >/dev/null && exec breadsearch listen'", "label": "Search command bus (breadsearch listen)", "enabled": true },

View file

@ -63,6 +63,16 @@ end
hl.window_rule({ name = "breadhelp", match = { class = "^(com\\.breadway\\.breadhelp)$" }, float = true, size = { 880, 600 } }) hl.window_rule({ name = "breadhelp", match = { class = "^(com\\.breadway\\.breadhelp)$" }, float = true, size = { 880, 600 } })
hl.window_rule({ name = "bos-netsetup", match = { class = "^(bos-netsetup)$" }, float = true, size = { 700, 560 } }) hl.window_rule({ name = "bos-netsetup", match = { class = "^(bos-netsetup)$" }, float = true, size = { 700, 560 } })
-- ---------------------------------------------------------------------------
-- Layer-shell rules — blur / ignore-alpha / per-namespace motion for the
-- bread shell (breadbar island + osd + notif + panel, breadbox launcher,
-- breadclip popup). Theme-driven via ~/.config/hypr/layerrules.json (written
-- by `bread-theme layerrules` in the bootstrap below), with a hardcoded
-- fallback so a missing/broken theme file can never leave the shell
-- unblurred. Without these the launcher and popups get no compositor blur.
-- ---------------------------------------------------------------------------
pcall(dofile, script_dir .. "ui/rules.lua")
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
-- Environment (vendor-neutral; no GPU-specific vars so it works on Intel/AMD). -- Environment (vendor-neutral; no GPU-specific vars so it works on Intel/AMD).
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
@ -76,6 +86,15 @@ hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
hl.env("SDL_VIDEODRIVER", "wayland") hl.env("SDL_VIDEODRIVER", "wayland")
hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto") hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto")
hl.env("_JAVA_AWT_WM_NONREPARENTING", "1") hl.env("_JAVA_AWT_WM_NONREPARENTING", "1")
-- GTK4's default renderer (ngl/vulkan on GTK 4.14+) renders transparent
-- layer-shell surfaces as opaque black on wlroots — the whole bread shell
-- (breadbox launcher, breadclip popup, breadbar, breadhelp, bos-settings)
-- goes black-on-black, and it's worse under a VM's virtio-gpu where no real
-- GL/Vulkan context is available. cairo (software) always composites
-- transparency correctly; the shell is flat colour/text/icons so there's no
-- visible perf cost, and idle memory drops (~40%, no Mesa driver resident).
-- Set GSK_RENDERER=gl in the session to override for a GPU-heavy GTK4 app.
hl.env("GSK_RENDERER", "cairo")
-- Optional NVIDIA env from bos-nvidia-setup. Mesa machines have no file. -- Optional NVIDIA env from bos-nvidia-setup. Mesa machines have no file.
-- bos-nvidia-setup: optional proprietary env; no-op when the file is absent -- bos-nvidia-setup: optional proprietary env; no-op when the file is absent
@ -128,6 +147,12 @@ hl.on("hyprland.start", function()
-- Generate the shared bread GUI stylesheet first, so breadbar/breadbox/ -- Generate the shared bread GUI stylesheet first, so breadbar/breadbox/
-- bos-settings load it on start (they also live-reload if it changes). -- bos-settings load it on start (they also live-reload if it changes).
"bread-theme generate", "bread-theme generate",
-- Write ~/.config/hypr/layerrules.json from the active shell theme's
-- [compositor] table. scripts/ui/rules.lua (loaded above) already ran
-- with the hardcoded fallback; this refreshes the JSON for the next
-- `hyprctl reload` / theme switch. Harmless if bread-theme lacks the
-- verb on an older build.
"sh -c 'bread-theme layerrules 2>/dev/null || true'",
-- Global dark theme: GTK4/libadwaita + GTK3 theme + icon + cursor. -- 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 color-scheme prefer-dark",
"gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark", "gsettings set org.gnome.desktop.interface gtk-theme Adwaita-dark",
@ -137,9 +162,9 @@ hl.on("hyprland.start", function()
-- Clipboard history is breadclipd, a bakery-managed systemd --user -- Clipboard history is breadclipd, a bakery-managed systemd --user
-- service (auto-started from /usr/lib/systemd/user — see -- service (auto-started from /usr/lib/systemd/user — see
-- build-local.sh's service bake) rather than an exec-once here. -- build-local.sh's service bake) rather than an exec-once here.
-- Prefer bread-polkit when bakery has published it; otherwise the -- Prefer bread-polkit if it is on PATH (not baked; lockfile does not
-- ISO's polkit-gnome agent. command -v so a missing binary does not -- ship it). Otherwise the ISO's polkit-gnome agent. command -v so a
-- leave the session without an auth agent. -- missing binary does not leave the session without an auth agent.
"sh -c 'if command -v bread-polkit >/dev/null; then exec bread-polkit; else exec /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1; fi'", "sh -c 'if command -v bread-polkit >/dev/null; then exec bread-polkit; else exec /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1; fi'",
"awww-daemon", "awww-daemon",
-- Set the default wallpaper once the daemon is up (retry until ready). -- Set the default wallpaper once the daemon is up (retry until ready).
@ -194,6 +219,7 @@ hl.on("hyprland.start", function()
"breadhelp --autostart", "breadhelp --autostart",
"bash -c 'command -v breadpaper >/dev/null && exec breadpaper listen'", "bash -c 'command -v breadpaper >/dev/null && exec breadpaper listen'",
"bash -c 'command -v breadshot >/dev/null && exec breadshot listen'", "bash -c 'command -v breadshot >/dev/null && exec breadshot listen'",
"bash -c 'command -v breadlock >/dev/null && exec breadlock listen'",
"bash -c 'command -v breadbox >/dev/null && exec breadbox listen'", "bash -c 'command -v breadbox >/dev/null && exec breadbox listen'",
"bash -c 'command -v breadhelp >/dev/null && exec breadhelp listen'", "bash -c 'command -v breadhelp >/dev/null && exec breadhelp listen'",
"bash -c 'command -v breadsearch >/dev/null && exec breadsearch listen'", "bash -c 'command -v breadsearch >/dev/null && exec breadsearch listen'",

View file

@ -22,6 +22,7 @@ local DEFAULT_EXTRA = {
{ command = "breadhelp --autostart", enabled = true }, { command = "breadhelp --autostart", enabled = true },
{ command = "bash -c 'command -v breadpaper >/dev/null && exec breadpaper listen'", enabled = true }, { command = "bash -c 'command -v breadpaper >/dev/null && exec breadpaper listen'", enabled = true },
{ command = "bash -c 'command -v breadshot >/dev/null && exec breadshot listen'", enabled = true }, { command = "bash -c 'command -v breadshot >/dev/null && exec breadshot listen'", enabled = true },
{ command = "bash -c 'command -v breadlock >/dev/null && exec breadlock listen'", enabled = true },
{ command = "bash -c 'command -v breadbox >/dev/null && exec breadbox listen'", enabled = true }, { command = "bash -c 'command -v breadbox >/dev/null && exec breadbox listen'", enabled = true },
{ command = "bash -c 'command -v breadhelp >/dev/null && exec breadhelp listen'", enabled = true }, { command = "bash -c 'command -v breadhelp >/dev/null && exec breadhelp listen'", enabled = true },
{ command = "bash -c 'command -v breadsearch >/dev/null && exec breadsearch listen'", enabled = true }, { command = "bash -c 'command -v breadsearch >/dev/null && exec breadsearch listen'", enabled = true },

View file

@ -0,0 +1,136 @@
-- scripts/ui/rules.lua — layer-shell (compositor) rules for the bread shell.
--
-- Blur / ignore-alpha / per-namespace motion for breadbar (island + osd +
-- notif + panel + dismiss), the breadbox launcher, and the breadclip popup.
-- Window rules (float/centre for onboarding popups) live in hyprland.lua;
-- this file is layer rules only.
--
-- Theme-driven (bos-ui-demos THEME_SYSTEM_PLAN.md §9): `bread-theme
-- layerrules` writes ~/.config/hypr/layerrules.json from the active shell
-- theme's [compositor] table — blur / ignore_alpha / blur_popups /
-- animation / no_anim, keyed by layer-shell namespace. That table owns
-- *appearance* only; placement / workspace / focus are never touched here.
-- If the JSON is missing, unreadable, malformed, or anything goes wrong
-- while parsing it, apply_hardcoded_layer_rules() runs instead — the exact
-- set this file hardcoded before the theme system existed — so a broken or
-- absent theme file can never disable compositor blur.
local LAYERRULES_JSON = os.getenv("HOME") .. "/.config/hypr/layerrules.json"
local function apply_hardcoded_layer_rules()
hl.layer_rule({
name = "breadbar-island",
match = { namespace = "^breadbar$" },
blur = true,
ignore_alpha = 0.2,
blur_popups = true,
animation = "slide top",
})
hl.layer_rule({
name = "breadbar-osd",
match = { namespace = "^breadbar-osd$" },
blur = true,
ignore_alpha = 0.2,
animation = "slide bottom",
})
hl.layer_rule({
name = "breadbar-notif",
match = { namespace = "^breadbar-notif$" },
blur = true,
ignore_alpha = 0.2,
animation = "slide right",
})
hl.layer_rule({
name = "breadbar-panel",
match = { namespace = "^breadbar-panel$" },
blur = true,
ignore_alpha = 0.2,
animation = "slide right",
})
hl.layer_rule({
name = "breadbar-dismiss",
match = { namespace = "^breadbar-dismiss$" },
no_anim = true,
})
hl.layer_rule({
match = "breadbox",
blur = true,
ignore_alpha = 0.2,
})
end
-- Parses ~/.config/hypr/layerrules.json into a plain array of per-namespace
-- rule tables, sorted by namespace for a deterministic emission order.
-- Returns nil (not an error) for anything short of a well-formed, non-empty
-- JSON object, so the pcall wrapping this only guards against a genuine Lua
-- error (e.g. json.lua failing to load) rather than every malformed case.
local function parsed_layer_rules()
local json = dofile(os.getenv("HOME") .. "/.config/hypr/scripts/lib/json.lua")
local parsed = json.load(LAYERRULES_JSON)
if type(parsed) ~= "table" then
return nil
end
local namespaces = {}
for ns, rule in pairs(parsed) do
if type(ns) == "string" and type(rule) == "table" then
namespaces[#namespaces + 1] = ns
end
end
if #namespaces == 0 then
return nil
end
table.sort(namespaces)
local rules = {}
for _, ns in ipairs(namespaces) do
local r = parsed[ns]
rules[#rules + 1] = {
namespace = ns,
blur = r.blur == true,
ignore_alpha = r.ignore_alpha,
blur_popups = r.blur_popups == true,
animation = r.animation,
no_anim = r.no_anim == true,
}
end
return rules
end
-- Emits one hl.layer_rule per parsed namespace. Built as pure data by
-- parsed_layer_rules() first (no hl.layer_rule calls during parsing), so a
-- parse failure can never leave a partial JSON-derived rule set applied
-- before the caller falls back to apply_hardcoded_layer_rules().
local function apply_json_layer_rules(rules)
for _, r in ipairs(rules) do
hl.layer_rule({
name = r.namespace,
match = { namespace = "^" .. r.namespace .. "$" },
blur = r.blur,
ignore_alpha = r.ignore_alpha,
blur_popups = r.blur_popups,
animation = r.animation,
no_anim = r.no_anim,
})
end
end
local ok, rules = pcall(parsed_layer_rules)
if ok and rules then
apply_json_layer_rules(rules)
else
apply_hardcoded_layer_rules()
end
-- breadclip is not part of the shell theme's [compositor] table yet — always
-- hardcoded, regardless of the JSON above.
hl.layer_rule({
match = "breadclip",
blur = true,
ignore_alpha = 0.0,
})

View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
# Run by mkarchiso inside the airootfs chroot, after packages are installed
# and before the squashfs is built. (archiso prints a deprecation warning for
# this hook, but there is no non-deprecated replacement for "trust an extra
# pacman repo key in the image keyring", and BOS ships no pacman-init.service.)
#
# Purpose: trust the BOS release key (56203B86…) in the image's pacman
# keyring so the signed [breadway] repo (SigLevel = Required,
# https://dl.breadway.dev/arch) verifies both on the live medium and — via
# calamares' unpackfs, which copies this squashfs to the target — on the
# installed system. calamares/post-install.sh re-does this in the target
# chroot as a fallback (unpackfs can skip /etc/pacman.d/gnupg).
set -euo pipefail
BREADWAY_KEY_FPR="56203B86A110695AE7F310934AF3323D678EB5E2"
KEY_FILE="/etc/pacman.d/breadway-repo.asc"
pacman-key --init
pacman-key --populate archlinux
if [[ -f "$KEY_FILE" ]]; then
pacman-key --add "$KEY_FILE"
pacman-key --lsign-key "$BREADWAY_KEY_FPR"
echo "customize_airootfs: trusted [breadway] repo key $BREADWAY_KEY_FPR"
else
echo "customize_airootfs: WARNING $KEY_FILE missing; [breadway] will not verify" >&2
fi

View file

@ -47,7 +47,15 @@ fi
echo echo
bold "==> Bread ecosystem (bakery update --all)" bold "==> Bread ecosystem (bakery update --all)"
if command -v bakery >/dev/null; then if command -v bakery >/dev/null; then
bakery update --all || echo "WARN: bakery update failed" # /usr/local is root-owned. Never run bakery as the user against it;
# bakery itself also tries sudo -n then pkexec for privileged writes.
if sudo -n true >/dev/null 2>&1; then
sudo -n bakery update --all || echo "WARN: bakery update failed"
elif command -v pkexec >/dev/null; then
pkexec bakery update --all || echo "WARN: bakery update failed"
else
echo "WARN: bakery update needs sudo -n or pkexec for /usr/local"
fi
else else
echo "bakery not found; skipping" echo "bakery not found; skipping"
fi fi

View file

@ -3,8 +3,8 @@
# build-local.sh and CI (scripts/ci-stage-bakery.py) read this file. A missing # build-local.sh and CI (scripts/ci-stage-bakery.py) read this file. A missing
# *required* binary fails the bake: a hollow ISO is worse than a failed build. # *required* binary fails the bake: a hollow ISO is worse than a failed build.
# optional_bins are baked when the verified stable index publishes them, and # optional_bins are baked when the verified stable index publishes them, and
# skipped with a warning when it does not (today: bread 0.8.0 has no # skipped with a warning when it does not. bread 0.8.0 ships bread-emit and
# bread-emit / bread-module-host). # bread-module-host, so those are required_bins.
# #
# A flat `bins` list is still accepted and treated as required_bins. # A flat `bins` list is still accepted and treated as required_bins.
# #
@ -23,6 +23,8 @@ required_bins = [
"bakery", "bakery",
"bread", "bread",
"breadd", "breadd",
"bread-emit",
"bread-module-host",
"breadman", "breadman",
"breadbar", "breadbar",
"breadbox", "breadbox",
@ -41,19 +43,13 @@ required_bins = [
"breadhelp", "breadhelp",
] ]
# Bake if the verified index publishes them; do not fail the ISO if absent.
optional_bins = [
"bread-emit",
"bread-module-host",
]
# Package name → version. Must exist at dl.breadway.dev/<pkg>/<ver>/ and # Package name → version. Must exist at dl.breadway.dev/<pkg>/<ver>/ and
# should match the signed index so CI can verify sha256. # should match the signed index so CI can verify sha256.
# [[pin]] { package, version } is accepted as well and merged (conflict = bake error). # [[pin]] { package, version } is accepted as well and merged (conflict = bake error).
[versions] [versions]
bakery = "0.7.3" bakery = "0.7.4"
bread = "0.8.0" bread = "0.8.0"
bread-theme = "0.7.3" bread-theme = "0.7.4"
breadbar = "0.3.2" breadbar = "0.3.2"
breadbox = "0.3.2" breadbox = "0.3.2"
breadcrumbs = "2.1.8" breadcrumbs = "2.1.8"

View file

@ -225,6 +225,8 @@ slurp
wl-clipboard wl-clipboard
playerctl playerctl
# Wallpaper daemon + pywal (drives the bread* colour palette from the wallpaper). # Wallpaper daemon + pywal (drives the bread* colour palette from the wallpaper).
# python-pywal was dropped from Arch [extra] (AUR-only now) — republished to
# [breadway], see packaging/python-pywal.
awww awww
python-pywal python-pywal
# Boot splash (BOS logo + spinner instead of kernel text). # Boot splash (BOS logo + spinner instead of kernel text).

View file

@ -49,18 +49,17 @@ Include = /etc/pacman.d/mirrorlist
# are NOT here; they are bakery-baked into /usr/local at ISO build time. # are NOT here; they are bakery-baked into /usr/local at ISO build time.
# #
# Packages are published to the Forgejo Arch registry (group "os") by the # Packages are published to the Forgejo Arch registry (group "os") by the
# .forgejo/workflows/*.yml workflows in this repo (and breadlock's). # .forgejo/workflows/*.yml workflows; scripts/ci-publish-signed-repo.sh then
# collects them, detach-signs each .pkg.tar.zst with the BOS release key
# (releases@breadway.dev), runs `repo-add -s`, and publishes the signed db
# at https://dl.breadway.dev/arch/$arch (signed-repo.yml).
# #
# Forgejo's Arch package registry does not serve pacman-compatible db # SigLevel = Required: every package AND the db carry a .sig from key
# signatures. SigLevel = Never is TLS-only integrity: the connection is # 56203B86A110695AE7F310934AF3323D678EB5E2 — the same key committed as
# HTTPS (or rewritten to hestia's localhost:3002 in CI). breadlock (PAM) # KEYS.asc / airootfs/etc/pacman.d/breadway-repo.asc, imported into the
# rides this repo. Do NOT flip to SigLevel = Required unless a signed db # pacman keyring at build time (build-local.sh), on the live medium, and
# has been verified to work — Required without signatures breaks the ISO # on the installed target (calamares/post-install.sh).
# and every install that uses [breadway]. KEYS.asc is the ISO SHA256SUMS
# signing key, not a pacman repo key.
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# The section name must match Forgejo's served db filename [breadway]
# ({owner}.{group}.{domain}.db) — pacman fetches "<section>.db" from Server. SigLevel = Required
[Breadway.os.git.breadway.dev] Server = https://dl.breadway.dev/arch/$arch
SigLevel = Never
Server = https://git.breadway.dev/api/packages/Breadway/arch/os/$arch

View file

@ -3,7 +3,7 @@ Arch packaging
This directory only holds `PKGBUILD`s for third-party AUR packages BOS This directory only holds `PKGBUILD`s for third-party AUR packages BOS
republishes to the `[breadway]` pacman repo (`calamares`, `bibata`, republishes to the `[breadway]` pacman repo (`calamares`, `bibata`,
`powerlevel10k`, `yay-bin`) — not the user's own code. See each `powerlevel10k`, `yay-bin`, `python-pywal`) — not the user's own code. See each
subdirectory's `.forgejo/workflows/<name>.yml` (in this repo) for how each subdirectory's `.forgejo/workflows/<name>.yml` (in this repo) for how each
one publishes on a push to `packaging/<name>/**`. one publishes on a push to `packaging/<name>/**`.

View file

@ -0,0 +1,44 @@
# BOS in-house rebuild of python-pywal.
#
# python-pywal was dropped from Arch's [extra] repo (it is now AUR-only), but
# BOS needs the `wal` binary: bread-theme shells out to it to extract a colour
# palette from the user's wallpaper. Republished to [breadway] so the ISO can
# pull it via pacman, same pattern as calamares / bibata / powerlevel10k /
# yay-bin. Source of truth: https://aur.archlinux.org/packages/python-pywal
#
# Maintainer: Breadway <plasticbread849@gmail.com>
# Upstream maintainer: Morten Linderud <foxboron@archlinux.org>
# Contributor: Sean Haugh <seanphaugh@gmail.com>
pkgname=python-pywal
pkgver=3.3.0
pkgrel=11
pkgdesc="Generate and change colorschemes on the fly"
arch=('any')
url="https://github.com/dylanaraps/pywal/"
license=('MIT')
depends=('python' 'imagemagick')
makedepends=('python-build' 'python-installer' 'python-wheel' 'python-setuptools')
optdepends=('feh: set wallpaper'
'nitrogen: set wallpaper')
# BOS PKGBUILDs verify sources by sha256 only (no source PGP), matching
# calamares / powerlevel10k here.
source=("$pkgname-$pkgver.tar.gz::https://github.com/dylanaraps/pywal/archive/${pkgver}.tar.gz")
sha256sums=('fe8fc1c29d1cad1a1a8580293dcfe32e1fac259f9dbfd5c8877439fa5948d189')
build() {
cd "pywal-${pkgver}"
# setup.py-only project: python-build injects the setuptools backend.
python -m build --wheel --no-isolation
}
check() {
cd "pywal-${pkgver}"
python -m unittest discover -vs tests
}
package() {
cd "pywal-${pkgver}"
python -m installer --destdir="$pkgdir" dist/*.whl
install -Dm644 LICENSE.md "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

View file

@ -22,6 +22,7 @@ PACKAGES=(
bibata-cursor-theme-bin bibata-cursor-theme-bin
zsh-theme-powerlevel10k zsh-theme-powerlevel10k
yay-bin yay-bin
python-pywal
) )
ARCH="${BREADWAY_ARCH:-x86_64}" ARCH="${BREADWAY_ARCH:-x86_64}"
@ -81,6 +82,8 @@ repo_add_signed() {
fi fi
[[ -e breadway.db.tar.gz.sig || -e breadway.db.sig ]] \ [[ -e breadway.db.tar.gz.sig || -e breadway.db.sig ]] \
|| die "repo-add -s did not write breadway.db*.sig" || die "repo-add -s did not write breadway.db*.sig"
# gpg writes 0600; nginx and the next publish need world-readable files.
find . -maxdepth 1 -type f -exec chmod a+r {} + || true
} }
ensure_arch_tools() { ensure_arch_tools() {
@ -125,7 +128,12 @@ sign_and_index_anywhere() {
rt="$(container_runtime)" || die \ rt="$(container_runtime)" || die \
"need host gpg+repo-add, or docker/podman to run archlinux:latest (no Forgejo container: — host must see /srv/breadway-dl)" "need host gpg+repo-add, or docker/podman to run archlinux:latest (no Forgejo container: — host must see /srv/breadway-dl)"
# Host job + bind-mount, same reason bakery writes /srv without container:. # Host job + bind-mount, same reason bakery writes /srv without container:.
# Run as the runner user: root-owned 0600 .sig files made chmod/nginx fail
# (run 1050) and would block the next `rm -rf` of a previous tree.
"$rt" run --rm --network=host \ "$rt" run --rm --network=host \
--user "$(id -u):$(id -g)" \
-e HOME=/tmp \
-e TMPDIR=/tmp \
-e GPG_PRIVATE_KEY \ -e GPG_PRIVATE_KEY \
-e BREADWAY_SIGN_ONLY=1 \ -e BREADWAY_SIGN_ONLY=1 \
-e BREADWAY_REPO_DIR=/repo \ -e BREADWAY_REPO_DIR=/repo \
@ -243,8 +251,10 @@ publish_tree() {
parent="$(dirname "$DEST")" parent="$(dirname "$DEST")"
dest_name="$(basename "$DEST")" dest_name="$(basename "$DEST")"
mkdir -p "$parent" mkdir -p "$parent"
chmod a+rX "$STAGE" chmod a+rX "$STAGE" || true
find "$STAGE" -type f -exec chmod a+r {} + # gpg --detach-sign often writes 0600 files the runner cannot chmod;
# do not fail the publish after repo-add -s already succeeded.
find "$STAGE" -type f -exec chmod a+r {} + || true
prev="$parent/${dest_name}.prev" prev="$parent/${dest_name}.prev"
rm -rf "$prev" rm -rf "$prev"
if [[ -e "$DEST" ]]; then if [[ -e "$DEST" ]]; then

View file

@ -40,22 +40,15 @@ check "grub-btrfs present" "pacman -Qq grub-btrfs"
echo "== enabled system services ==" echo "== enabled system services =="
for unit in NetworkManager.service greetd.service bluetooth.service tlp.service \ for unit in NetworkManager.service greetd.service bluetooth.service tlp.service \
cups.socket avahi-daemon.service ufw.service systemd-timesyncd.service; do cups.socket avahi-daemon.socket ufw.service systemd-timesyncd.service; do
check "$unit enabled" "systemctl is-enabled $unit" check "$unit enabled" "systemctl is-enabled $unit"
done done
check "graphical.target is default" "[ \"\$(systemctl get-default)\" = graphical.target ]" check "graphical.target is default" "[ \"\$(systemctl get-default)\" = graphical.target ]"
echo "== bread ecosystem on PATH ==" echo "== bread ecosystem on PATH =="
for bin in bakery bread breadd breadbar breadbox breadbox-sync breadcrumbs breadpad breadman; do for bin in bakery bread breadd bread-emit bread-module-host breadbar breadbox breadbox-sync breadcrumbs breadpad breadman; do
check "$bin found" "command -v $bin" check "$bin found" "command -v $bin"
done done
for bin in bread-emit bread-module-host; do
if command -v "$bin" >/dev/null 2>&1; then
ok "$bin found"
else
note "$bin not on PATH (optional until stable bread ships it)"
fi
done
echo "== bos-settings ==" echo "== bos-settings =="
check "bos-settings installed" "command -v bos-settings" check "bos-settings installed" "command -v bos-settings"