Build 18745 produced a working image (8.00 GiB compressed to 972 MiB in 44
seconds, checksum recorded) and then failed on the very last step:
GHESNotSupportedError: @actions/artifact v2.0.0+, upload-artifact@v4+ and
download-artifact@v4+ are not currently supported on GHES.
Gitea's artifact backend identifies as GHES, and the v2 artifact client behind
upload-artifact@v4 refuses GHES outright rather than falling back. I chose v4
on the assumption Gitea 1.25 supported it; it does not. v3 works.
Dropping compression-level with it — v3 has no such input, and the image is
already zstd so it would have been a no-op anyway.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
Build 18744 died at "Building filesystems" with:
mkfs.vfat: file /work/esp.img already exists
esp.img was left there by build 18700, which was cancelled during compression
and so never reached its end-of-build cleanup. Making $WORK persist so the
staged base could be reused made everything else in it persist as well, and
that turns out not to be inert: mkfs.vfat -C refuses to overwrite, and
`mv $ROOTFS/boot $WORK/boot` would have nested inside a surviving directory
rather than replacing it — a subtler failure that would have produced a /boot
filesystem containing a stray boot/ subdirectory.
Clear the work directory at the start of each run, keeping only the staged base
and its stamp, so the invariant is stated positively rather than depending on
the previous run having exited cleanly. Cleanup that only runs on success is
not cleanup.
Also record why 95-set-boot-entry.install exits 1 during the transaction, so
the next person to read a build log does not go hunting. It is a consequence of
suppressing initramfs generation, is confined to the build, and cannot occur on
the device.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
The first real run took 100 minutes. Roughly 60 of those were spent doing
things that either did not need doing or did not need doing under emulation.
dracut ran three times. The kernel's %posttrans runs kernel-install, which
builds an initramfs, and dracut-config-rescue makes it build a second, rescue
one — both before this build has written /etc/dracut.conf.d/10-c630.conf and
before /proc is bind-mounted, so both are wrong as well as expensive. stage2
then builds the real one. Setting initrd_generator=none in the install root for
the duration of the transaction suppresses both: 50-dracut.install and
51-dracut-rescue.install each bail when KERNEL_INSTALL_INITRD_GENERATOR is not
"dracut". Excluding dracut-config-rescue also spares the laptop a rescue
initramfs on every future kernel update, which on this hardware is not cheap.
That file must not ship. With it in place the machine would boot fine and then
fail to come back after its next kernel update — a bug that surfaces weeks
later looking nothing like an image problem. It is removed from the working
copy, and asserted absent again immediately before the root filesystem is
built.
Compression was running as an aarch64 binary under qemu-user for no reason;
zstd does not care what architecture it runs on. It now runs on the host when
the host has zstd, falling back to in-container otherwise so the build never
depends on it. Measured ~20 minutes against ~2.
Add BASE_RECIPE to the staged-base stamp. Excluding a weak dependency changes
what the base contains without changing the package list it hashes, so without
this the next build would happily reuse a stale base.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
I had this wrong. The claim that accelerated graphics works on the firmware
Fedora ships does not survive contact with the device tree.
Fedora does package an sdm845/a630_zap.mbn, which is what led me astray, but
that is the generic Snapdragon 845 zap shader. The C630's node in
sdm850-lenovo-yoga-c630.dts names qcom/sdm850/LENOVO/81JL/qcdxkmsuc850.mbn
specifically — model-signed, present only in the machine's Windows partition.
Until it is supplied the display is unaccelerated.
Also add qcslpi850.mbn and qcvss850.mbn, which were missing entirely, and
attribute the sensor hub to slpi_pas rather than cdsp_pas. The list is now
every firmware-name property in the mainline device tree rather than a
recollection of forum posts, so it should be complete: eight files, not five.
WiFi and Bluetooth are unaffected — ath10k WCN3990 including wlanmdsp.mbn is
genuinely redistributable and genuinely shipped.
Add a section on locating the blobs on an old backup, for the case where
Windows is long gone from the machine.
Only comments changed in config/packages/base.pkgs, so the package set hashes
identically and the staged base stays valid.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
The first run produced one job leg with an empty variant, and the build
rejected it. The prepare job was fine — it set variants=["minimal"] and the
output evaluated correctly — but Gitea's runner resolves strategy.matrix while
planning the workflow, before `needs` has produced anything, so
fromJSON(needs.prepare.outputs.variants) saw an empty string.
Dynamic matrices from needs outputs are not supported here. Drop the prepare
job and loop in shell instead: one job, no expression-engine dependency, and a
non-zero exit only after every requested variant has been attempted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
The dnf transaction is essentially the whole cost of a build — emulated rpm
scriptlets for 502 packages on minimal, 1929 on workstation. Everything after
it is minutes. Getting this laptop to boot will take several attempts at the
kernel command line and the dracut driver list, and paying for a reinstall each
time is not tenable.
Stage the post-dnf tree under <work>/base, keyed on a hash of the package
lists, release and variant, and copy it per build with --reflink=auto (a CoW
clone on btrfs). Config and overlay edits now reuse it; package list edits
invalidate it on their own, so --fresh is only needed to force the issue.
Keep downloaded rpms in a cachedir outside the install root, so even --fresh
re-runs the scriptlets without re-downloading. keepcache=0 was exactly the
wrong setting for a build meant to be run repeatedly.
Add --work so CI can put both outside the job workspace, which is wiped between
runs, and default the build container to the gongfoo aarch64 build base so the
assembly tooling is not installed under emulation every time. That image is a
speedup, not a dependency: fall back to stock Fedora when it is unreachable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS
Assembles a ready-to-write disk image via Gitea Actions. Mainline has carried
sdm850-lenovo-yoga-c630.dts since 5.5 and Fedora ships it in kernel-core, so
unlike aarch64-laptops/build there is no kernel or GRUB to compile — what is
left is producing an image that boots on firmware which hands Linux no device
tree.
The build runs in an aarch64 container under qemu-user and builds filesystems
from directory trees with mke2fs -d and mcopy rather than mounting loop
devices, so it works on runners that will not hand out /dev/loop-control.
A kernel-install hook writes the devicetree line into each BLS entry; without
it the first `dnf update kernel` would produce an unbootable system.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWRjNJMistCy6ngXH5aJLS