diff --git a/.forgejo/workflows/python-pywal.yml b/.forgejo/workflows/python-pywal.yml new file mode 100644 index 0000000..f5f7d61 --- /dev/null +++ b/.forgejo/workflows/python-pywal.yml @@ -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" diff --git a/.forgejo/workflows/signed-repo.yml b/.forgejo/workflows/signed-repo.yml index 3ef8a65..caa5d15 100644 --- a/.forgejo/workflows/signed-repo.yml +++ b/.forgejo/workflows/signed-repo.yml @@ -21,6 +21,7 @@ on: - Build and publish bibata-cursor-theme - Build and publish powerlevel10k - Build and publish yay-bin + - Build and publish python-pywal types: [completed] concurrency: diff --git a/DESIGN.md b/DESIGN.md index 31b22f8..6a075a8 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -16,7 +16,7 @@ taken as current: | 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. | | 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. | --- diff --git a/README.md b/README.md index 1a833b6..22b0108 100644 --- a/README.md +++ b/README.md @@ -174,10 +174,10 @@ dedicated release-signing key (not reused from anything else): 5620 3B86 A110 695A E7F3 1093 4AF3 323D 678E B5E2 ``` -The public half is committed at [`KEYS.asc`](KEYS.asc). That key signs -**ISO checksums only** — it does not sign the `[breadway]` pacman repo -(Forgejo's Arch registry has no pacman-compatible db signatures; that -section stays `SigLevel = Never` until a signed repo exists — see +The public half is committed at [`KEYS.asc`](KEYS.asc). The same key signs +the ISO checksums **and** the `[breadway]` pacman repo — every package and +the db at `https://dl.breadway.dev/arch` carry a `.sig` from it, and that +section is `SigLevel = Required` (see [docs/signed-repo.md](docs/signed-repo.md)). To verify a download: ```sh @@ -381,8 +381,9 @@ until `dl.breadway.dev/arch` exists). - **Snapshots assume btrfs**: the snapper/grub-btrfs tooling expects the default btrfs subvolume layout the installer creates. Recovery is the GRUB snapshots submenu, not `snapper rollback` — [docs/hardware.md](docs/hardware.md). -- **`[breadway]` signatures**: `SigLevel = Never` until a signed repo is - stood up at `dl.breadway.dev/arch`. See [docs/signed-repo.md](docs/signed-repo.md). +- **`[breadway]` signatures**: `SigLevel = Required` — the signed repo at + `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 diff --git a/build-local.sh b/build-local.sh index e360d42..4b95661 100755 --- a/build-local.sh +++ b/build-local.sh @@ -25,14 +25,19 @@ OUT="${OUT:-$REPO/out}" STAGE=/tmp/bos-iso-stage rm -rf "$STAGE" && cp -a "$REPO/iso" "$STAGE" -# Rewrite the [breadway] pacman repo URL to the fastest reachable address. -# CI_BUILD=1 — container runs on hestia with --network=host; localhost:3002 is direct -# default — building on hermes; git.breadway.dev is flaky from there, use Tailscale -# Only ever rewrites the staged copy, never the committed pacman.conf. -if [ "${CI_BUILD:-0}" = "1" ]; then - sed -i 's#https://git.breadway.dev/api/packages/Breadway/arch/os#http://localhost:3002/api/packages/Breadway/arch/os#' "$STAGE/pacman.conf" -else - sed -i 's#https://git.breadway.dev/api/packages/Breadway/arch/os#http://100.66.238.26:3002/api/packages/Breadway/arch/os#' "$STAGE/pacman.conf" +# [breadway] now points at the signed public repo https://dl.breadway.dev/arch +# (SigLevel = Required) — no Forgejo-registry URL rewrite needed anymore. +# +# Trust the [breadway] repo key in *this* build host's pacman keyring so +# `pacstrap` can verify [breadway] packages while assembling the airootfs. +# The same key is baked into the image at etc/pacman.d/breadway-repo.asc and +# re-trusted on the live medium / installed target (calamares/post-install.sh). +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 if [ "${FAST_BUILD:-0}" = "1" ]; then diff --git a/docs/signed-repo.md b/docs/signed-repo.md index 02b5340..bda8656 100644 --- a/docs/signed-repo.md +++ b/docs/signed-repo.md @@ -1,18 +1,20 @@ # Signed `[breadway]` repo -Today the ISO's `[Breadway.os.git.breadway.dev]` section is -`SigLevel = Never`. That is TLS-only integrity: packages come from Forgejo's -Arch registry, which does **not** serve pacman-compatible database -signatures. `KEYS.asc` signs **ISO `SHA256SUMS`** and, once published, the -`dl.breadway.dev/arch` database. It is **not** imported as a pacman repo key -on the ISO yet. Do not flip `SigLevel` to `Required` on that section until -a signed repo exists and has been verified; Required without signatures -breaks the ISO and every installed system. +**Status: live.** The ISO's `[breadway]` section is `SigLevel = Required` +and points at `https://dl.breadway.dev/arch/$arch`, where every +`.pkg.tar.zst` and the db carry a detached `.sig` from the BOS release key +(`56203B86…`, `KEYS.asc`, `releases@breadway.dev`). That key is trusted in +the pacman keyring at build time (`build-local.sh`), on the live medium +(`iso/airootfs/root/customize_airootfs.sh`), and on the installed target +(`iso/airootfs/etc/calamares/post-install.sh`). -The signed repo belongs at `https://dl.breadway.dev/arch`, not on Forgejo's -registry. Forgejo publishing stays as it is (`package.yml` / packaging -workflows PUT unsigned `.pkg.tar.zst` so existing Never installs keep -working). +Forgejo publishing is unchanged: `package.yml` / packaging workflows still +PUT unsigned `.pkg.tar.zst` to Forgejo's Arch registry. The signed tree at +`dl.breadway.dev/arch` is rebuilt from that registry by +`.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` @@ -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 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 `BREADWAY_PKG_DIR` on the runner overrides individual files. @@ -74,7 +76,7 @@ Pacman fetches `
.db` + `
.db.sig` from `Server`. ## Dispatch the 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`): @@ -83,13 +85,11 @@ curl -fsS -X POST \ -H "Authorization: token ${RELEASE_TOKEN}" \ -H "Content-Type: application/json" \ "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 -(`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 `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 `/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 -and the verify commands above succeed: +All three steps have landed: -1. Import `KEYS.asc` into the ISO keyring (`pacman-key --add` + `--lsign-key`). -2. Point `[breadway]` `Server` at `https://dl.breadway.dev/arch/$arch`. -3. Only then flip that section to `SigLevel = Required`. +1. **Key trusted.** The public key is committed at + `iso/airootfs/etc/pacman.d/breadway-repo.asc`. `build-local.sh` + `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 -`iso/pacman.conf` and `iso/airootfs/etc/pacman.conf`. This branch does -**not** change either file. +### Re-verify after any build + +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…`. diff --git a/iso/airootfs/etc/calamares/modules/welcome.conf b/iso/airootfs/etc/calamares/modules/welcome.conf index 33bf7ad..c31ea71 100644 --- a/iso/airootfs/etc/calamares/modules/welcome.conf +++ b/iso/airootfs/etc/calamares/modules/welcome.conf @@ -3,9 +3,19 @@ showSupportUrl: false showKnownIssuesUrl: 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: requiredStorage: 20 requiredRam: 2.0 - checkInternet: true - checkPower: true - internetCheckUrl: "https://archlinux.org" + internetCheckUrl: "https://breadway.dev" + check: + - storage + - ram + - power + - internet + - root + required: + - storage + - ram + - root diff --git a/iso/airootfs/etc/calamares/post-install.sh b/iso/airootfs/etc/calamares/post-install.sh index 5817f2e..d7df9be 100644 --- a/iso/airootfs/etc/calamares/post-install.sh +++ b/iso/airootfs/etc/calamares/post-install.sh @@ -8,8 +8,6 @@ # 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)" - # Whether Calamares encrypted the root partition (LUKS) — checked once here, # used below to conditionally wire mkinitcpio's encrypt hook and GRUB's # 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 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 # packages (Calamares + archiso boot chain + memtest/EFI-shell payloads) so # 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 # system unable to verify package signatures — the first `pacman -Syu` then dies # with "keyring is not writable / required key missing". Initialise it here so a -# fresh install can update out of the box. archlinux-keyring is already present -# and is the only keyring populated — it verifies official Arch packages. -# [breadway] stays SigLevel=Never (Forgejo does not serve pacman-compatible -# db signatures). Do not import KEYS.asc here: that key signs ISO SHA256SUMS, -# not the pacman repo; treating it as a repo key would be a lie. +# fresh install can update out of the box. archlinux-keyring verifies official +# Arch packages; the BOS release key (56203B86…, shipped at +# /etc/pacman.d/breadway-repo.asc) verifies the signed [breadway] repo at +# dl.breadway.dev/arch — SigLevel = Required there, every package and the db +# carry a .sig from it. # --------------------------------------------------------------------------- +BREADWAY_KEY_FPR="56203B86A110695AE7F310934AF3323D678EB5E2" if command -v pacman-key &>/dev/null; then pacman-key --init || echo "WARN: pacman-key --init failed" pacman-key --populate archlinux || echo "WARN: pacman-key --populate failed" + 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 # --------------------------------------------------------------------------- diff --git a/iso/airootfs/etc/pacman.conf b/iso/airootfs/etc/pacman.conf index 4e4435e..2f53c10 100644 --- a/iso/airootfs/etc/pacman.conf +++ b/iso/airootfs/etc/pacman.conf @@ -32,18 +32,17 @@ Include = /etc/pacman.d/mirrorlist # 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 -# .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 -# signatures. SigLevel = Never is TLS-only integrity: the connection is -# HTTPS (or rewritten to hestia's localhost:3002 in CI). breadlock (PAM) -# rides this repo. Do NOT flip to SigLevel = Required unless a signed db -# has been verified to work — Required without signatures breaks the ISO -# and every install that uses [breadway]. KEYS.asc is the ISO SHA256SUMS -# signing key, not a pacman repo key. +# SigLevel = Required: every package AND the db carry a .sig from key +# 56203B86A110695AE7F310934AF3323D678EB5E2 — the same key committed as +# KEYS.asc / etc/pacman.d/breadway-repo.asc, imported into the pacman +# keyring at build time (build-local.sh), on the live medium, and on the +# installed target (calamares/post-install.sh). # ----------------------------------------------------------------------- -# 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 +[breadway] +SigLevel = Required +Server = https://dl.breadway.dev/arch/$arch diff --git a/iso/airootfs/etc/pacman.d/breadway-repo.asc b/iso/airootfs/etc/pacman.d/breadway-repo.asc new file mode 100644 index 0000000..fe380fd --- /dev/null +++ b/iso/airootfs/etc/pacman.d/breadway-repo.asc @@ -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----- diff --git a/iso/airootfs/etc/skel/.config/bread/modules/external-monitors.lua b/iso/airootfs/etc/skel/.config/bread/modules/external-monitors.lua new file mode 100644 index 0000000..27a276c --- /dev/null +++ b/iso/airootfs/etc/skel/.config/bread/modules/external-monitors.lua @@ -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 diff --git a/iso/airootfs/etc/skel/.config/hypr/autostart.json b/iso/airootfs/etc/skel/.config/hypr/autostart.json index ee66107..be2b4ac 100644 --- a/iso/airootfs/etc/skel/.config/hypr/autostart.json +++ b/iso/airootfs/etc/skel/.config/hypr/autostart.json @@ -7,6 +7,7 @@ { "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 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 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 }, diff --git a/iso/airootfs/etc/skel/.config/hypr/hyprland.lua b/iso/airootfs/etc/skel/.config/hypr/hyprland.lua index 565da5b..cfa488b 100644 --- a/iso/airootfs/etc/skel/.config/hypr/hyprland.lua +++ b/iso/airootfs/etc/skel/.config/hypr/hyprland.lua @@ -63,6 +63,16 @@ end 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 } }) +-- --------------------------------------------------------------------------- +-- 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). -- --------------------------------------------------------------------------- @@ -76,6 +86,15 @@ 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") +-- 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. -- 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/ -- bos-settings load it on start (they also live-reload if it changes). "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. "gsettings set org.gnome.desktop.interface color-scheme prefer-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 -- service (auto-started from /usr/lib/systemd/user — see -- build-local.sh's service bake) rather than an exec-once here. - -- Prefer bread-polkit when bakery has published it; otherwise the - -- ISO's polkit-gnome agent. command -v so a missing binary does not - -- leave the session without an auth agent. + -- Prefer bread-polkit if it is on PATH (not baked; lockfile does not + -- ship it). Otherwise the ISO's polkit-gnome agent. command -v so a + -- 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'", "awww-daemon", -- Set the default wallpaper once the daemon is up (retry until ready). @@ -194,6 +219,7 @@ hl.on("hyprland.start", function() "breadhelp --autostart", "bash -c 'command -v breadpaper >/dev/null && exec breadpaper 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 breadhelp >/dev/null && exec breadhelp listen'", "bash -c 'command -v breadsearch >/dev/null && exec breadsearch listen'", diff --git a/iso/airootfs/etc/skel/.config/hypr/scripts/system/autostart.lua b/iso/airootfs/etc/skel/.config/hypr/scripts/system/autostart.lua index 46f4970..63e364f 100644 --- a/iso/airootfs/etc/skel/.config/hypr/scripts/system/autostart.lua +++ b/iso/airootfs/etc/skel/.config/hypr/scripts/system/autostart.lua @@ -22,6 +22,7 @@ local DEFAULT_EXTRA = { { command = "breadhelp --autostart", 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 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 breadhelp >/dev/null && exec breadhelp listen'", enabled = true }, { command = "bash -c 'command -v breadsearch >/dev/null && exec breadsearch listen'", enabled = true }, diff --git a/iso/airootfs/etc/skel/.config/hypr/scripts/ui/rules.lua b/iso/airootfs/etc/skel/.config/hypr/scripts/ui/rules.lua new file mode 100644 index 0000000..79e0a2f --- /dev/null +++ b/iso/airootfs/etc/skel/.config/hypr/scripts/ui/rules.lua @@ -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, +}) diff --git a/iso/airootfs/root/customize_airootfs.sh b/iso/airootfs/root/customize_airootfs.sh new file mode 100644 index 0000000..149c6c4 --- /dev/null +++ b/iso/airootfs/root/customize_airootfs.sh @@ -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 diff --git a/iso/airootfs/usr/local/bin/bos-update b/iso/airootfs/usr/local/bin/bos-update index 16b9da8..a34f70b 100644 --- a/iso/airootfs/usr/local/bin/bos-update +++ b/iso/airootfs/usr/local/bin/bos-update @@ -47,7 +47,15 @@ fi echo bold "==> Bread ecosystem (bakery update --all)" 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 echo "bakery not found; skipping" fi diff --git a/iso/bread-lockfile.toml b/iso/bread-lockfile.toml index 6154893..8e21593 100644 --- a/iso/bread-lockfile.toml +++ b/iso/bread-lockfile.toml @@ -3,8 +3,8 @@ # 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. # 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 -# bread-emit / bread-module-host). +# skipped with a warning when it does not. bread 0.8.0 ships bread-emit and +# bread-module-host, so those are required_bins. # # A flat `bins` list is still accepted and treated as required_bins. # @@ -23,6 +23,8 @@ required_bins = [ "bakery", "bread", "breadd", + "bread-emit", + "bread-module-host", "breadman", "breadbar", "breadbox", @@ -41,19 +43,13 @@ required_bins = [ "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/// and # should match the signed index so CI can verify sha256. # [[pin]] { package, version } is accepted as well and merged (conflict = bake error). [versions] -bakery = "0.7.3" +bakery = "0.7.4" bread = "0.8.0" -bread-theme = "0.7.3" +bread-theme = "0.7.4" breadbar = "0.3.2" breadbox = "0.3.2" breadcrumbs = "2.1.8" diff --git a/iso/packages.x86_64 b/iso/packages.x86_64 index ff90edf..d30488a 100644 --- a/iso/packages.x86_64 +++ b/iso/packages.x86_64 @@ -225,6 +225,8 @@ slurp wl-clipboard playerctl # 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 python-pywal # Boot splash (BOS logo + spinner instead of kernel text). diff --git a/iso/pacman.conf b/iso/pacman.conf index be3d52f..abb7e2f 100644 --- a/iso/pacman.conf +++ b/iso/pacman.conf @@ -49,18 +49,17 @@ Include = /etc/pacman.d/mirrorlist # 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 -# .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 -# signatures. SigLevel = Never is TLS-only integrity: the connection is -# HTTPS (or rewritten to hestia's localhost:3002 in CI). breadlock (PAM) -# rides this repo. Do NOT flip to SigLevel = Required unless a signed db -# has been verified to work — Required without signatures breaks the ISO -# and every install that uses [breadway]. KEYS.asc is the ISO SHA256SUMS -# signing key, not a pacman repo key. +# SigLevel = Required: every package AND the db carry a .sig from key +# 56203B86A110695AE7F310934AF3323D678EB5E2 — the same key committed as +# KEYS.asc / airootfs/etc/pacman.d/breadway-repo.asc, imported into the +# pacman keyring at build time (build-local.sh), on the live medium, and +# on the installed target (calamares/post-install.sh). # ----------------------------------------------------------------------- -# 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 +[breadway] +SigLevel = Required +Server = https://dl.breadway.dev/arch/$arch diff --git a/packaging/arch/README.md b/packaging/arch/README.md index ce80f7e..1116280 100644 --- a/packaging/arch/README.md +++ b/packaging/arch/README.md @@ -3,7 +3,7 @@ Arch packaging This directory only holds `PKGBUILD`s for third-party AUR packages BOS 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/.yml` (in this repo) for how each one publishes on a push to `packaging//**`. diff --git a/packaging/python-pywal/PKGBUILD b/packaging/python-pywal/PKGBUILD new file mode 100644 index 0000000..b6796f5 --- /dev/null +++ b/packaging/python-pywal/PKGBUILD @@ -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 +# Upstream maintainer: Morten Linderud +# Contributor: Sean Haugh + +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" +} diff --git a/scripts/ci-publish-signed-repo.sh b/scripts/ci-publish-signed-repo.sh index 08fbb53..6ded0e0 100755 --- a/scripts/ci-publish-signed-repo.sh +++ b/scripts/ci-publish-signed-repo.sh @@ -22,6 +22,7 @@ PACKAGES=( bibata-cursor-theme-bin zsh-theme-powerlevel10k yay-bin + python-pywal ) ARCH="${BREADWAY_ARCH:-x86_64}" @@ -81,6 +82,8 @@ repo_add_signed() { fi [[ -e breadway.db.tar.gz.sig || -e 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() { @@ -125,7 +128,12 @@ sign_and_index_anywhere() { 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)" # 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 \ + --user "$(id -u):$(id -g)" \ + -e HOME=/tmp \ + -e TMPDIR=/tmp \ -e GPG_PRIVATE_KEY \ -e BREADWAY_SIGN_ONLY=1 \ -e BREADWAY_REPO_DIR=/repo \ @@ -243,8 +251,10 @@ publish_tree() { parent="$(dirname "$DEST")" dest_name="$(basename "$DEST")" mkdir -p "$parent" - chmod a+rX "$STAGE" - find "$STAGE" -type f -exec chmod a+r {} + + chmod a+rX "$STAGE" || true + # 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" rm -rf "$prev" if [[ -e "$DEST" ]]; then diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index ccdc571..28b5c28 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -40,22 +40,15 @@ check "grub-btrfs present" "pacman -Qq grub-btrfs" echo "== enabled system services ==" for unit in NetworkManager.service greetd.service bluetooth.service tlp.service \ - cups.socket avahi-daemon.service ufw.service systemd-timesyncd.service; do + cups.socket avahi-daemon.socket ufw.service systemd-timesyncd.service; do check "$unit enabled" "systemctl is-enabled $unit" done check "graphical.target is default" "[ \"\$(systemctl get-default)\" = graphical.target ]" echo "== bread ecosystem on PATH ==" -for bin in bakery bread breadd breadbar breadbox breadbox-sync breadcrumbs breadpad breadman; do +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" 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 ==" check "bos-settings installed" "command -v bos-settings"