Author SHA1 Message Date
alvinlollo e57143c4bc docs/ci: implement yay 13 SIGSEGV workaround and update docs
Upstream pin check / quickshell-pin (push) Successful in 28s
Tests / tests (push) Successful in 3m31s
2026-10-12 12:57:37 +11:00
alvinlollo 3275ec9845 chore: add build runner CAP_SETFCAP check, fix MicroTeX compilation, and fix mono cert-sync
Upstream pin check / quickshell-pin (push) Successful in 18s
Tests / tests (push) Successful in 2m55s
2026-10-11 19:41:08 +11:00
alvinlolloandClaude Opus 5.5 b68623a782 docs: Document the zsh-autocomplete partial-load error and fixes
Upstream pin check / quickshell-pin (push) Successful in 20s
Tests / tests (push) Successful in 3m0s
Adds a Troubleshooting entry (symptom, cause, drop-or-keep fixes). The
keep path removes grml-zsh-config instead of setting
skip_global_compinit=1: grml's /etc/zsh/zshrc runs compinit
unconditionally and ignores that Debian/Ubuntu variable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:08:30 +11:00
alvinlolloandClaude Opus 5.5 88e73bbc22 docs: Sync AGENTS.md with CI lint, fish default, zshrc staging
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:41:39 +11:00
alvinlolloandClaude Opus 5.5 025082a92f fix(localrepo): Exact local package match, no .old on medium, rebuild on bump
- tests: match localrepo packages by exact name (foo no longer satisfied
  by foo-bar-*.pkg.tar.zst).
- update.sh: copy the db/files by name so repo-add's *.old backups stay
  off the medium.
- build-end4-deps.sh: rebuild a local PKGBUILD when it is newer than the
  built package (a quickshell _commit bump used to be skipped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:41:38 +11:00
alvinlolloandClaude Opus 5.5 699ed6a935 fix(installer): Validate hostname, parse disk models, skip zram
- screen_hostname: reject names that break /etc/hostname and /etc/hosts.
- screen_disk: parse lsblk -P by regex so multi-word/empty MODELs show
  correctly, and never offer /dev/zram* (TYPE=disk) as a target.
- packages: add python-distro, the undeclared runtime dep of the shipped
  ssh-import-id (it always crashed and fell back to curl).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:41:38 +11:00
alvinlolloandClaude Opus 5.5 e63f9cb14f fix(iso): Ship an archiso preset for linux-cachyos, the kernel we boot
linux.preset targeted the 'linux' package, which is not on the ISO, so
linux-cachyos generated its own default+fallback preset and the squashfs
carried an unused fallback initramfs. No package owns the preset file
(mkinitcpio's hook only creates one if missing), so this cannot conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:41:38 +11:00
alvinlolloandClaude Opus 5.5 c5c7ab9b8b fix(ci): Lint extensionless scripts, serialize tests, drop pinned URLs
- ShellCheck now also covers bash scripts without .sh (alvin-install,
  choose-mirror, livecd-sound), picked up by shebang.
- concurrency group: the job rewrites the runner's /etc/pacman.conf.
- Import the CachyOS key from the keyserver instead of pacman -U on
  pinned keyring/mirrorlist URLs that break once the mirror prunes them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:39:22 +11:00
alvinlolloandClaude Opus 5.5 03f0ca05e0 fix(build): Verify cachyos and chaotic-aur signatures at ISO build time
Both repos used SigLevel = Optional TrustAll, so unsigned or tampered
packages went straight into the ISO. Inherit Required DatabaseOptional
from [options] and have build-iso import + lsign both signing keys.

Also in build-iso: detach only loop devices backed by the work dir
(losetup -D hit every loop on the runner host) and drop the ?token=
download fallback that put the API token into access logs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:39:22 +11:00
alvinlolloandClaude Opus 5.5 5299b90bbc fix(skel): Keep script exec bits and ship a .zshrc for oh-my-zsh
- Replace the 644/755 chmod sweep (which also undid its own chmod 700)
  with go-w,a+rX so non-.sh executables keep +x.
- Install an oh-my-zsh .zshrc into /etc/skel after pacstrap. It is staged
  as /root/.zshrc-skel because grml-zsh-config owns /etc/skel/.zshrc and
  an airootfs copy would abort pacstrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:38:11 +11:00
alvinlolloandClaude Opus 5.5 55b66a1373 fix(installer): Survive copytoram, keep ISO repos, default to fish
- install_pacstrap: fall back to the live system's pacman -Q when the
  medium's pkglist is gone (copytoram unmounts bootmnt), and die on an
  empty list instead of pacstrapping only the essentials.
- screen_disk: find the live medium via archisosearchuuid when bootmnt
  is unmounted, and hide whole-disk sources (empty PKNAME).
- install_cachyos_repo: put [cachyos] ahead of [core] like the ISO build
  and add [chaotic-aur], so chaotic packages keep updating.
- install_user: create the user with fish; the 'none' login manager now
  autostarts Hyprland via ~/.config/fish/conf.d/alvin-hyprland.fish.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:38:11 +11:00
alvinlolloandClaude Opus 5.5 504a568570 chore(localrepo): Stop tracking makepkg output and repo-add backups
build-end4-deps.sh builds in place, so pkg/, src/ and the VCS mirrors
under end4-pkgbuilds/ (incl. a built quickshell tree) ended up in git.
Ignore everything there except PKGBUILDs and hooks, plus *.old backups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:38:00 +11:00
alvinlolloandClaude Opus 5.5 0ae2f036b7 fix(ci): Stop bash -e from killing the conflict check silently
Tests / tests (push) Successful in 3m29s
The `pacman -S` in the conflict check exits non-zero by design (a
conflict fails in prepare, a clean list fails at the disabled download),
and `OUT=$(...)` carries that status, so `bash -e` aborted the step
before the output was inspected: exit 1 with no message on every run.
Ignore the status and judge by the output, as intended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:25:17 +11:00
alvinlolloandClaude Opus 5.5 0e57df4360 fix(ci): Make the package conflict check actually detect conflicts
Tests / tests (push) Failing after 2m53s
The step ran `pacman -Sp`, which skips conflict checking entirely: it
happily prints URLs for eternalterminal + eternalterminal-client and
exits 0. It also skipped every localrepo package, so the conflict fixed
in f06bfd1 could never have been caught here.

Now the check resolves against a throwaway config that adds [local-aur]
(localrepo/), with a fresh dbpath/root so runner-installed packages
don't mask or fake conflicts, and runs a real `pacman -S`. Conflicts and
broken deps fail in "prepare", before any download; XferCommand is set
to /usr/bin/false after the sync so a clean list stops at the download
instead of fetching the whole ISO. Package availability comes from one
`-Slq` instead of ~400 `-Si` calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:50:35 +11:00
alvinlolloandClaude Opus 5.5 da26e88147 fix(localrepo): Drop stale packages so update.sh can't re-add them
f06bfd1 and a940d91 removed eternalterminal-client/-server,
illogical-impulse-quickshell-git, meshy-git and pacserve from
packages.x86_64, but their .pkg.tar.zst files stayed in localrepo/ and
in local-aur.db. update.sh appends every localrepo package name back to
the list, which would bring the eternalterminal conflict back, and CI
shipped the conflicting packages into the image's /opt/localrepo.

Remove the files and their repo DB entries, re-sort the vulkan-* lines
in packages.x86_64, and drop the names from README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:39:06 +11:00
alvinlollo f06bfd1512 fix(ci): Remove conflicting packages
Tests / tests (push) Successful in 4m36s
2026-10-08 21:44:54 +11:00
alvinlollo a940d91794 fix(ci): Fix end4 dependecies
Tests / tests (push) Successful in 4m35s
2026-10-08 20:53:54 +11:00
alvinlollo 9af4bc6c27 fix(tests): Fix yamlint compliance with whitespace
Tests / tests (push) Successful in 4m31s
2026-10-08 19:01:25 +11:00
alvinlollo 3c540f8bed fix(test): Fix yaml formatting
Tests / tests (push) Failing after 24s
2026-10-08 18:55:27 +11:00
alvinlollo 3f3bba05f3 fix(test): Fix formatting
Tests / tests (push) Failing after 25s
2026-10-08 18:53:51 +11:00
alvinlollo 01913f7682 feat(jobs): Output conflicting packages
Tests / tests (push) Failing after 2m24s
2026-10-08 18:46:47 +11:00
alvinlollo f9a2978517 Update MicroTeX submodule pointer
Tests / tests (push) Failing after 2m30s
2026-10-08 18:35:41 +11:00
alvinlollo 1e8a1e3282 fix(ci): Update Qt version to latest
Upstream pin check / quickshell-pin (push) Successful in 46s
Tests / tests (push) Failing after 2m54s
2026-10-08 18:19:34 +11:00
alvinlollo db83549996 feat(gum): Add further additions of gum to install script
Tests / tests (push) Successful in 2m29s
2026-10-08 14:48:20 +11:00
alvinlollo 59d82f06f5 feat(installer): sshd opt-in, gsettings replay, live-medium motd stamp
Tests / tests (push) Successful in 2m50s
- alvin-install: new sshd screen (default on) + install step. openssh is
  guaranteed into the pacstrap list when requested, and the final banner
  reports whether the enable actually succeeded (SSHD_APPLIED) instead of
  what was selected. The prompt warns that password login uses the same
  password as LUKS, and only mentions the GitHub key import when a
  username was given.
- alvin-install: replay the `gsettings set` commands end-4 logged during
  its setup run with a clean env and a throwaway session bus as the target
  user, then read each key back in a second session to confirm it persisted
  (gvariant_unescape normalizes both '...' and "..." GVariant output). The
  venv check now walks the uv bin/python symlink chain, re-rooting
  absolute hops under $TARGET, since that link dangles on the live medium.
- build: append the build stamp to the live /etc/motd (keeps the installed
  system's motd untouched) and drop alvin-install's install_motd step.
- docs: installer sections in README/AGENTS.md, sshd notes, corrected line
  counts.
2026-10-07 21:55:30 +11:00
alvinlollo 1bb6f8dddd ci: install archiso via private pacman config, not /etc/pacman.conf
Tests / tests (push) Successful in 4m8s
Writing stock core/extra over the host /etc/pacman.conf raced with the
tests workflow, which appends [cachyos]/[chaotic-aur] to the same file —
runs 132 and 133 ran concurrently on the runner, so either job could
corrupt the other's config mid-pacman.

Use 'pacman --config $(mktemp)' with stock core/extra instead: same
DNS-lookup reduction (only core/extra sync, no cachyos/chaotic), zero
writes to shared state. Verified on the runner: exit 0, only core/extra
synced, host /etc/pacman.conf mtime unchanged.
2026-10-07 11:22:12 +11:00
alvinlollo 0fbec83573 ci: retry pacman syncs and reset host pacman.conf before build
Tests / tests (push) Successful in 5m7s
Run 131 failed at 'pacman -Syu archiso jq' because the gateway
(192.168.1.1) intermittently drops UDP/53: cachyos.db and chaotic-aur.db
lookups hit libalpm's 10s resolve timeout while core/extra survived on
resolved's cache.

- build-iso: reset the host /etc/pacman.conf to stock core/extra first
  (tests.yaml leaves [cachyos]/[chaotic-aur] behind, and mkarchiso reads
  the profile's pacman.conf anyway), pre-warm mirror hostnames in the
  resolver cache, then retry pacman -Syu 5x with backoff.
- tests: wrap all three pacman -Syu calls in the same retry loop.

DNS-side redundancy was added on the runner separately (networkd drop-in
adds 1.1.1.1/8.8.8.8 next to the gateway).
2026-10-07 11:14:28 +11:00
alvinlollo a6c99b72d0 fix(ci): ensure /dev/fd symlinks exist for mkinitcpio and procsub
Tests / tests (push) Successful in 2m33s
The chroot's /dev only gains fd/stdin/stdout/stderr symlinks at boot, and
container executors often lack them, which breaks mkinitcpio's preset step
(pipes through /dev/stdin) and bash process substitution
("/dev/fd/63: No such file or directory").

- build-iso workflow: create the links in the executor's /dev, then mount a
  fresh devtmpfs and create them there too so the chroot tree is covered
  whether the executor's /dev is devtmpfs or a plain tmpfs
- customize_airootfs.sh: self-heal the same links inside the chroot
2026-10-06 14:53:16 +11:00
alvinlollo c64c64dc97 feat(installer): ask GitHub username first in git screen
Tests / tests (push) Successful in 2m14s
screen_git now prompts GitHub username before git name/email, and the
screen description plus installation summary list SSH before git to
match the new prompt order. Behavior unchanged - prompts are
independent and all assignment sites are read after the screen returns.
2026-10-05 17:42:15 +11:00
alvinlollo 319333e400 fix(packages): rename syncthingtray-qt6, ship neofetch from localrepo
Tests / tests (push) Successful in 2m23s
CI package check failed on main (400/402):

- syncthingtray-qt6 no longer exists in the AUR (RPC returns nothing);
  the qt6 build is now the official 'syncthingtray' package in extra/
  cachyos - rename it in packages.x86_64 and drop the dead
  AUR_MISSING entry from build-end4-deps.sh
- neofetch left the distro repos (only chaotic-neofetch-git remains);
  build the AUR package locally (7.1.0-2, PGP-verified against Dylan
  Araps' key) and commit it to localrepo/ with a reindexed local-aur
  db so both the CI check (file glob) and pacstrap ([local-aur]) work
- un-indent the package lists in build-end4-deps.sh: CI accepts
  repo-less packages via 'grep -qxF <name>' (exact whole-line match),
  which could never match indented names

Verified locally with an exact replica of the CI check: 402/402.
2026-10-05 17:32:23 +11:00
alvinlollo bb6f64e977 feat(installer): build-stamp motd, LUKS mode A/B, TPM2, unl0kr, login managers
Tests / tests (push) Failing after 2m33s
- profiledef.sh stages airootfs/root/.iso-build-stamp (git/CI gated, falls
  back to 'commit unknown'); customize_airootfs.sh installs it as
  /etc/alvin-build-stamp; install_motd appends it to the target /etc/motd
- alvin-install: skippable git/GitHub screens with ssh-import-id import
  (yay build + python-distro, curl .keys fallback), LUKS Mode A (systemd
  sd-encrypt + rd.luks.name) vs Mode B (unl0kr + cryptdevice), TPM2
  enrollment via $PASSWORD with rd.luks.options activation, unl0kr
  build/verify with Mode-A fallback, login manager none/greetd/sddm/gdm
  (enable DM before disabling getty@tty1), autologin per DM (forced off
  when TPM enrolled), build-stamp motd; includes the existing
  GRUB_DISABLE_BOOTNEXT grub tweak
- review fixes: greetd autologin TOML quoting, .ssh chowned before
  ssh-import-id, builtin-aware initramfs module verification across all
  installed kernels, curl exit-code split (fetch failure vs no keys),
  plain Hyprland on every login path
- add curl to packages.x86_64; document stamp flow and installer in
  AGENTS.md/README.md
2026-10-05 16:26:12 +11:00
28 changed files with 2423 additions and 217 deletions
+141 -12
View File
@@ -41,15 +41,143 @@ jobs:
- name: Install archiso and dependencies
run: |
set -euo pipefail
pacman -Syu --noconfirm --needed archiso jq
# Install with a *private* config instead of /etc/pacman.conf.
# The host config is shared mutable state: the tests workflow
# rewrites it and jobs overlap on this runner (runs 132 and 133
# ran concurrently), so writing it here could corrupt a concurrent
# tests run. It also carries [cachyos] and [chaotic-aur] leftovers
# from tests.yaml, and syncing those two repos means extra DNS
# lookups against a gateway that intermittently drops UDP/53 —
# that is what failed run 131 at this very step. The build only
# needs archiso/jq from extra; mkarchiso reads the *profile's*
# pacman.conf for everything that goes into the ISO.
BUILD_CONF=$(mktemp)
trap 'rm -f "$BUILD_CONF"' EXIT
cat > "$BUILD_CONF" << 'STOCKCONF'
[options]
DisableSandbox
HoldPkg = pacman glibc
Architecture = auto
CheckSpace
ParallelDownloads = 5
SigLevel = Required DatabaseOptional
LocalFileSigLevel = Optional
- name: Ensure /dev/stdin exists for mkinitcpio
[core]
Include = /etc/pacman.d/mirrorlist
[extra]
Include = /etc/pacman.d/mirrorlist
STOCKCONF
sed -i 's/^ //' "$BUILD_CONF"
# Warm the resolver cache for the mirrors we are about to query.
# systemd-resolved serves later lookups from cache, so a gateway
# UDP/53 burst cannot hit them again. Failures here are not fatal —
# the retry loop below is the real safety net.
for host in mirror.aarnet.edu.au gsl-syd.mm.fcix.net au.arch.niranjan.co; do
for try in 1 2 3 4 5; do
if getent hosts "$host" >/dev/null 2>&1; then
break
fi
echo " ⏳ DNS for $host failed (try $try/5), retrying in $(( try * 3 ))s..."
sleep $(( try * 3 ))
done
done
# The gateway drops DNS queries in bursts; one burst must not fail
# the whole job, so retry with backoff before giving up.
synced=0
for try in 1 2 3 4 5; do
if pacman --config "$BUILD_CONF" -Syu --noconfirm --needed archiso jq; then
synced=1
break
fi
echo " ⚠️ pacman -Syu failed (attempt $try/5), retrying in $(( try * 15 ))s..." >&2
sleep $(( try * 15 ))
done
if [ "$synced" -ne 1 ]; then
echo "❌ pacman -Syu failed after 5 attempts — giving up" >&2
exit 1
fi
- name: Trust cachyos and chaotic-aur signing keys
run: |
set -euo pipefail
# Docker/container environments may lack /dev/stdin; mkinitcpio needs it
[ -e /dev/stdin ] || ln -sf /proc/self/fd/0 /dev/stdin
[ -e /dev/stdout ] || ln -sf /proc/self/fd/1 /dev/stdout
[ -e /dev/stderr ] || ln -sf /proc/self/fd/2 /dev/stderr
# The profile's pacman.conf verifies [cachyos] and [chaotic-aur]
# signatures (SigLevel inherited from [options]); mkarchiso's
# pacstrap checks them against the host keyring.
# F3B607488DB35A47 CachyOS <admin@cachyos.org>
# 3056513887B78AEB chaotic-aur (Pedro Henrique Lara Campos)
for key in F3B607488DB35A47 3056513887B78AEB; do
if pacman-key --list-keys "$key" >/dev/null 2>&1; then
echo " ✅ $key already in the keyring"
else
got=0
# Retry: gateway drops UDP/53 in bursts (see run 131)
for try in 1 2 3 4 5; do
if pacman-key --recv-keys "$key" --keyserver keyserver.ubuntu.com; then
got=1
break
fi
echo " ⚠️ recv-keys $key failed (attempt $try/5), retrying in $(( try * 15 ))s..." >&2
sleep $(( try * 15 ))
done
if [ "$got" -ne 1 ]; then
echo "❌ Could not fetch signing key $key" >&2
exit 1
fi
fi
pacman-key --lsign-key "$key"
done
- name: Ensure /dev/fd symlinks exist for mkinitcpio and procsub
run: |
set -euo pipefail
# mkinitcpio's preset step pipes through /dev/stdin and bash process
# substitution (< <(...)) uses /dev/fd; both break if these symlinks
# are missing (they are normally created once at boot by mkinitcpio's
# init). Two places need them, so cover both:
# 1. the executor's own /dev: mkarchiso reads /dev/stdin on the host
# (machine-id, version, pkglist) and the workflow's own scripts use
# process substitution here. A no-op if the links already exist.
ln -sfn /proc/self/fd /dev/fd
ln -sfn /proc/self/fd/0 /dev/stdin
ln -sfn /proc/self/fd/1 /dev/stdout
ln -sfn /proc/self/fd/2 /dev/stderr
# 2. the chroot's /dev: devtmpfs is single-instance, so mounting it
# here yields the same tree that pacstrap/arch-chroot expose as
# <chroot>/dev. When the executor's /dev is itself devtmpfs (1)
# already fixed that tree; this pass is what makes it work when
# the executor's /dev is a plain tmpfs or directory instead.
d="$(mktemp -d)"
trap 'umount "$d" 2>/dev/null || true; rmdir "$d" 2>/dev/null || true' EXIT
mount -t devtmpfs devtmpfs "$d"
ln -sfn /proc/self/fd "$d/fd"
ln -sfn /proc/self/fd/0 "$d/stdin"
ln -sfn /proc/self/fd/1 "$d/stdout"
ln -sfn /proc/self/fd/2 "$d/stderr"
ls -l "$d/fd" "$d/stdin" "$d/stdout" "$d/stderr"
umount "$d"
rmdir "$d"
trap - EXIT
- name: Require CAP_SETFCAP for file capabilities
run: |
set -euo pipefail
# Without CAP_SETFCAP, pacstrap cannot restore security.capability
# xattrs ("unable to set CAP_SETFCAP effective capability"), so the
# live ISO silently ships newuidmap/newgidmap, snap-confine, dumpcap,
# kwin_wayland, ... without their capabilities. Fix the runner
# (e.g. allow CAP_SETFCAP in the gitea-runner service) rather than
# building a broken image.
eff="$(awk '/^CapEff:/{print $2}' /proc/self/status)"
if (( (16#$eff >> 31) & 1 )); then
echo "✅ CAP_SETFCAP is available (CapEff=$eff)"
else
echo "❌ CAP_SETFCAP missing (CapEff=$eff) - file capabilities would be dropped from the ISO" >&2
exit 1
fi
- name: Build Arch ISO
run: |
@@ -422,13 +550,11 @@ jobs:
url="$GITEA_ORIGIN/$REPOSITORY/releases/download/$TAG_NAME/$enc_fname"
downloaded=0
for attempt in 1 2 3; do
# Try header auth, then ?token=, then anonymous (public repo).
# Try header auth, then anonymous (public repo). No ?token=
# fallback: a token in the query string lands in access logs.
if curl -sfL --max-time 600 -H "Authorization: token $GITEA_TOKEN" \
"$url" -o "$tmpdir/$fname"; then
downloaded=1
elif curl -sfL --max-time 600 "$url?token=$GITEA_TOKEN" \
-o "$tmpdir/$fname"; then
downloaded=1
elif curl -sfL --max-time 600 "$url" -o "$tmpdir/$fname"; then
downloaded=1
fi
@@ -491,8 +617,11 @@ jobs:
# Unmount any leftover loop devices or bind mounts in the work directory
$PRIV umount -R "$PWD/work" 2>/dev/null || true
# Clean up loop devices if any remain attached to the work dir
$PRIV losetup -D || true
# Detach only loop devices backed by files in our work dir:
# `losetup -D` would detach every loop device on the runner host.
$PRIV losetup -nO NAME,BACK-FILE 2>/dev/null \
| awk -v w="$PWD/work/" 'index($2, w) == 1 {print $1}' \
| while read -r dev; do $PRIV losetup -d "$dev" || true; done
# Remove working directories with proper permissions
$PRIV rm -rf "$PWD/work"
+26
View File
@@ -0,0 +1,26 @@
name: Upstream pin check
on:
schedule:
- cron: "0 6 * * 1" # Mondays 06:00 UTC — catches upstream/Qt drift between ISO builds
workflow_dispatch:
push:
paths:
- "localrepo/end4-pkgbuilds/quickshell-git/PKGBUILD"
- "localrepo/check-quickshell-pin.sh"
- "airootfs/root/customize_airootfs.sh"
- ".gitea/workflows/pin-check.yaml"
jobs:
quickshell-pin:
runs-on: archlinux
steps:
- name: Checkout repo
uses: actions/checkout@v4
# Tripwire for the failure mode that broke ISO build run 137:
# a quickshell pin (ours or end-4's) that predates a Qt compat fix
# or has simply gone stale. Fails the job so the drift is visible
# in Actions instead of surfacing as a 3-hour ISO build failure.
- name: Check quickshell pins
run: ./localrepo/check-quickshell-pin.sh
+129 -27
View File
@@ -5,6 +5,12 @@ on:
pull_request:
workflow_dispatch:
# This job rewrites the runner's shared /etc/pacman.conf - never let two
# runs do that at the same time.
concurrency:
group: host-pacman-conf
cancel-in-progress: false
jobs:
tests:
runs-on: archlinux
@@ -36,25 +42,52 @@ jobs:
rm -f /var/lib/pacman/sync/cachyos* /var/lib/pacman/sync/chaotic*
- name: Install prerequisites
run: pacman -Syu --noconfirm --needed shellcheck yamllint
run: |
# The gateway intermittently drops UDP/53 (it failed build run 131
# at the equivalent step); retry with backoff instead of dying on
# the first bad DNS burst.
synced=0
for try in 1 2 3 4 5; do
if pacman -Syu --noconfirm --needed shellcheck yamllint; then
synced=1
break
fi
echo "⚠️ pacman -Syu failed (attempt $try/5), retrying in $(( try * 15 ))s..." >&2
sleep $(( try * 15 ))
done
if [ "$synced" -ne 1 ]; then
echo "❌ pacman -Syu failed after 5 attempts — giving up" >&2
exit 1
fi
- name: Set up CachyOS repo
run: |
# Key straight from the keyserver; no pinned keyring/mirrorlist
# package URLs (they 404 once the mirror prunes that version, and
# the mirrorlist was overwritten below anyway).
pacman-key --recv-keys F3B607488DB35A47 --keyserver keyserver.ubuntu.com
pacman-key --lsign-key F3B607488DB35A47
pacman -U --noconfirm \
'https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-keyring-20240331-1-any.pkg.tar.zst' \
'https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-mirrorlist-27-1-any.pkg.tar.zst'
# Fix the mirrorlist URL (pacnew has wrong format)
printf '%s\n' 'Server = https://mirror.cachyos.org/repo/$arch/$repo' \
> /etc/pacman.d/cachyos-mirrorlist
printf '\n[cachyos]\nInclude = /etc/pacman.d/cachyos-mirrorlist\n' \
>> /etc/pacman.conf
pacman -Syu --noconfirm --needed
# Retry: gateway drops UDP/53 in bursts (see run 131)
synced=0
for try in 1 2 3 4 5; do
if pacman -Syu --noconfirm --needed; then
synced=1
break
fi
echo "⚠️ pacman -Syu failed (attempt $try/5), retrying in $(( try * 15 ))s..." >&2
sleep $(( try * 15 ))
done
if [ "$synced" -ne 1 ]; then
echo "❌ pacman -Syu failed after 5 attempts — giving up" >&2
exit 1
fi
- name: Set up chaotic-aur repo
run: |
@@ -65,15 +98,34 @@ jobs:
printf '\n[chaotic-aur]\nInclude = /etc/pacman.d/chaotic-mirrorlist\n' \
>> /etc/pacman.conf
pacman -Syu --noconfirm --needed
# Retry: gateway drops UDP/53 in bursts (see run 131)
synced=0
for try in 1 2 3 4 5; do
if pacman -Syu --noconfirm --needed; then
synced=1
break
fi
echo "⚠️ pacman -Syu failed (attempt $try/5), retrying in $(( try * 15 ))s..." >&2
sleep $(( try * 15 ))
done
if [ "$synced" -ne 1 ]; then
echo "❌ pacman -Syu failed after 5 attempts — giving up" >&2
exit 1
fi
- name: Lint shell scripts with ShellCheck
run: |
echo "═══════════════════════════════════════════════════════════════"
echo " Running ShellCheck on all .sh files"
echo " Running ShellCheck on all bash scripts"
echo "═══════════════════════════════════════════════════════════════"
SH_FILES=$(find . -name '*.sh' -not -path './.git/*')
# *.sh plus extensionless bash scripts (alvin-install, choose-mirror,
# livecd-sound, ...) picked up by their shebang
SH_FILES=$(
find . -name '*.sh' -not -path './.git/*'
find airootfs/usr/local/bin -type f ! -name '*.sh' \
-exec grep -lE '^#!(/usr/bin/env bash|/bin/bash)' {} +
)
if [ -z "$SH_FILES" ]; then
echo "No shell scripts found."
exit 0
@@ -216,10 +268,18 @@ jobs:
done
# 2) Check localrepo/ for a pre-built .pkg.tar.zst
if [[ $MATCHED -eq 0 ]] && compgen -G "localrepo/${pkg}-*.pkg.tar.zst" > /dev/null 2>&1; then
MATCHED=1
FOUND=$((FOUND + 1))
echo "📦 LOCAL: $pkg (pre-built in localrepo/)"
# Exact name: strip -<pkgver>-<pkgrel>-<arch>.pkg.tar.zst, so
# "foo" is not satisfied by foo-bar-1.0-1-any.pkg.tar.zst
if [[ $MATCHED -eq 0 ]]; then
for f in localrepo/"${pkg}"-*.pkg.tar.zst; do
b=${f##*/}
if [[ "${b%-*-*-*}" == "$pkg" ]]; then
MATCHED=1
FOUND=$((FOUND + 1))
echo "📦 LOCAL: $pkg (pre-built in localrepo/)"
break
fi
done
fi
# 3) Check build-end4-deps.sh for the package name
@@ -267,23 +327,65 @@ jobs:
echo " Checking for conflicting packages in packages.x86_64"
echo "═══════════════════════════════════════════════════════════════"
# Collect all package names from the list
# Resolve against the repos mkarchiso sees, including [local-aur]
# (localrepo/), so conflicts between pre-built AUR packages and
# anything else are caught here instead of in the ISO build.
# A fresh dbpath = empty local db, like pacstrap's target root:
# packages installed on the runner can neither mask a conflict
# nor cause a false one.
CONF=$(mktemp)
DB=$(mktemp -d)
ROOT=$(mktemp -d)
cp /etc/pacman.conf "$CONF"
printf '\n[local-aur]\nSigLevel = Optional TrustAll\nServer = file://%s/localrepo\n' \
"$PWD" >> "$CONF"
mkdir -p "$DB/sync"
cp -p /var/lib/pacman/sync/*.db "$DB/sync/"
pac() { pacman --config "$CONF" --dbpath "$DB" "$@"; }
pac -Sy --noconfirm
# From here on never download (set after -Sy, which needs to fetch
# the file:// local-aur db): the check only needs a prepared
# transaction
sed -i '/^\[options\]/a XferCommand = /usr/bin/false %u' "$CONF"
# Packages built at ISO build time (build-end4-deps.sh) can't be
# resolved yet; everything else must be
mapfile -t AVAIL < <(pac -Slq | sort -u)
PKGS=()
while IFS= read -r pkg; do
[[ -z "$pkg" || "$pkg" =~ ^# ]] && continue
PKGS+=("$pkg")
if printf '%s\n' "${AVAIL[@]}" | grep -qxF -- "$pkg"; then
PKGS+=("$pkg")
else
echo "⚠️ Skipping unresolvable package: $pkg"
fi
done < packages.x86_64
# Use pacman -Sp --print to resolve the full install set and check for conflicts
# This catches "X and Y are in conflict" errors before the ISO build
PKG_LIST=$(printf '%s\n' "${PKGS[@]}")
if CONFLICTS=$(echo "$PKG_LIST" | pacman -Sp --needed - 2>&1); then
echo "✅ No package conflicts detected"
# `pacman -Sp` skips conflict checks entirely, so run a real -S
# into an empty root. Conflicts and broken deps fail in "prepare",
# before any download; a clean list gets as far as the download,
# which the XferCommand above makes fail on purpose.
if [ ${#PKGS[@]} -eq 0 ]; then
echo "✅ No resolvable packages to check."
else
echo "$CONFLICTS" | grep -i "conflict" || true
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo " ❌ Conflicting packages found — fix packages.x86_64"
echo "═══════════════════════════════════════════════════════════════"
exit 1
# pacman exits non-zero either way; judge by its output, not
# its status (bash -e would otherwise kill the step silently)
OUT=$(pac -S --noconfirm --root "$ROOT" --cachedir "$ROOT" "${PKGS[@]}" 2>&1) || true
if grep -q 'failed to prepare transaction' <<< "$OUT"; then
echo "------- Pacman Output -------"
grep -vE '^(http|file|ftp)' <<< "$OUT" | tail -40
echo "-----------------------------"
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo " ❌ Conflicting/unresolvable packages — fix packages.x86_64"
echo "═══════════════════════════════════════════════════════════════"
exit 1
elif ! grep -q 'Proceed with installation' <<< "$OUT"; then
echo "------- Pacman Output -------"
tail -40 <<< "$OUT"
echo "-----------------------------"
echo "❌ pacman stopped before the conflict check completed"
exit 1
fi
echo "✅ No package conflicts detected (repos + local-aur)"
fi
+12
View File
@@ -5,3 +5,15 @@ out/
# In-image copy of localrepo/ — populated by localrepo/update.sh and
# .gitea/workflows/build-iso.yaml, shipped as /opt/localrepo on the ISO
airootfs/opt/localrepo/
# Build stamp staged by profiledef.sh at ISO build time (see AGENTS.md);
# consumed by customize_airootfs.sh, never committed
airootfs/root/.iso-build-stamp
# makepkg output from localrepo/build-end4-deps.sh (it builds in place):
# keep only the PKGBUILDs and hooks under end4-pkgbuilds/
localrepo/end4-pkgbuilds/*/*
!localrepo/end4-pkgbuilds/*/PKGBUILD
!localrepo/end4-pkgbuilds/*/*.hook
# repo-add backups
localrepo/*.old
+51 -5
View File
@@ -22,7 +22,7 @@ ISO), so run it after touching `localrepo/` before building.
## CI Checks (must pass before merge)
- **ShellCheck**: all `.sh` files checked with `shellcheck -x -s bash`
- **ShellCheck**: all `.sh` files, plus extensionless bash scripts in `airootfs/usr/local/bin/` (by shebang), checked with `shellcheck -x -s bash`
- **yamllint**: all `.yml`/`.yaml` files (relaxed config)
- **packages.x86_64 syntax**: one package per line, no trailing whitespace, `[a-zA-Z0-9._@+\-]+` only
- **Package repo check**: every package in `packages.x86_64` must be resolvable via `pacman -Si` in allowed repos (`cachyos`, `extra`, `multilib`, `chaotic-aur`), OR have a pre-built `.pkg.tar.zst` in `localrepo/`, OR appear in `localrepo/build-end4-deps.sh`
@@ -49,26 +49,72 @@ install them into the target without the network.
| File | Purpose |
|------|---------|
| `packages.x86_64` | Live ISO package list (~402 packages) |
| `profiledef.sh` | ISO metadata, compression, boot modes |
| `profiledef.sh` | ISO metadata, compression, boot modes, build-stamp staging |
| `pacman.conf` | Repos: cachyos → core → extra → multilib → chaotic-aur → local-aur |
| `bootstrap_packages` | Minimal build-time packages (`base`, `arch-install-scripts`) |
| `airootfs/root/customize_airootfs.sh` | Runs inside chroot during build: keyrings, skel dotfiles (incl. end4-pC), qsConfig patch, end-4 `install-deps`, package harvest, uv/font pre-warm |
| `airootfs/root/.live-pacman.conf` | `/etc/pacman.conf` of the built image (adds `[local-aur]` = `file:///opt/localrepo`) |
| `airootfs/usr/local/bin/alvin-install` | Installer: partitions, pacstrap, user, end-4 dotfiles (`--skip-alldeps`), boot |
| `airootfs/usr/local/bin/alvin-install` | Installer: partitions, pacstrap, LUKS (systemd initramfs **or** experimental unl0kr + optional TPM2 unlock), user (fish shell), git identity + GitHub SSH keys, login manager (none/greetd/sddm/gdm) + autologin, optional sshd (default on), end-4 dotfiles (`--skip-alldeps`), boot |
| `localrepo/build-end4-deps.sh` | Non-interactive AUR builds for end-4 dependencies |
| `localrepo/update.sh` | Regenerates localrepo DB, syncs names to packages.x86_64, syncs `airootfs/opt/localrepo/` |
| `localrepo/check-quickshell-pin.sh` | Tripwire: our quickshell pin and end-4's must include the Qt 6.12 fix and stay <45d old (`QUICKSHELL_PIN_MAX_AGE_DAYS` overrides) |
| `.gitea/workflows/pin-check.yaml` | Runs the pin check weekly (Mon 06:00), on dispatch, and on pushes touching pins/`customize_airootfs.sh` |
## alvin-install (installer inside the ISO)
Source: `airootfs/usr/local/bin/alvin-install` (~1,880 lines, **no `.sh` extension**).
The airootfs overlay ships it as `/usr/local/bin/alvin-install` (mode `0755` forced in
`profiledef.sh` `file_permissions`), and `/etc/motd` advertises it on the live medium.
- **Invoke**: as root from the live session. Hard requirements: `gum` and UEFI —
`check_prereqs` exits if `/sys/firmware/efi` is missing. Log: `/var/log/alvin-install.log`
(copied into the target at the end; `ERR` trap prints the failing line).
- **Structure**: `main()` runs 16 `screen_*` TUI functions (collect answers: welcome, keymap,
network, disk, luks, unl0kr, filesystem, username, password, hostname, git, timezone,
locale, login manager, sshd, summary) then a fixed order of `install_*` steps
(partition → LUKS → format/mount → pacstrap → fstab → repos → timezone → locale →
hostname → network → sshd → user → git/SSH → end-4 dots → mkinitcpio → bootloader →
TPM/unl0kr → login manager → autologin → cleanup). Screens and install steps are
independent — edit one without
touching the other. An `EXIT` trap unmounts `$TARGET` and closes `cryptroot` on any exit.
- **Package list**: `install_pacstrap` builds the *installed* system's list from the medium's
`/run/archiso/bootmnt/arch/pkglist.x86_64.txt` (i.e. `pacman -Q` of the live ISO), so
`packages.x86_64` changes flow into installed systems. It drops `PKGS_EXCLUDE`
(`archiso mkinitcpio-archiso mkinitcpio-nfs-utils syslinux memtest86+`) plus names not
resolvable in the live repos, then always appends hardcoded essentials
(`base linux linux-firmware linux-cachyos networkmanager grub efibootmgr sudo git …`).
- **Linting**: CI's shellcheck step picks it up by its bash shebang. Locally:
`shellcheck -x -s bash airootfs/usr/local/bin/alvin-install`
- end-4 quirks (`--skip-alldeps`, `yes i`, end4-pC re-copy) are in [Gotchas](#gotchas) below.
## Verify a single change locally (mirrors CI)
```bash
shellcheck -x -s bash <script>.sh # plus the alvin-install command above
yamllint -d "{extends: relaxed, rules: {document-start: disable, line-length: disable, new-line-at-end-of-file: disable}}" <file>.yaml
```
`packages.x86_64` syntax: one package per line, `[a-zA-Z0-9._@+\-]+` only, no trailing
whitespace.
## Gotchas
- The profile root is the **repo root** (not a `releng/` subdirectory). The CI build-iso workflow uses `mkarchiso -v -w work -o out/ releng` but this is a naming convention — the actual profile files live at the repo root.
- The profile root is the **repo root** (not a `releng/` subdirectory). CI builds with `mkarchiso -v -w "$PWD/work" -o "$PWD/out" -- "$PWD"` (`.gitea/workflows/build-iso.yaml`), and the local build command above does the same — never pass a `releng` path.
- `customize_airootfs.sh` clones git repos (oh-my-zsh, lazy.nvim, plugins, end-4 dots) into `/etc/skel` and `/opt` during the ISO build. These require network access at build time but make the live ISO work offline.
- `customize_airootfs.sh` runs `./setup install-deps -f -s --skip-plasmaintg` as the throwaway user `ii-builder` (end-4's scripts refuse to run as root) and aborts the build if it fails. `--skip-plasmaintg` is deliberate: that optional Firefox widget pulls 410 packages / 526 MiB of KDE.
- The `[local-aur]` repo path in `pacman.conf` (`file:///home/alvin/alvinarch/localrepo`) is for local builds only. The CI does not use this path.
- The `[local-aur]` repo path in `pacman.conf` (`file:///home/alvin/AlvinArch/localrepo`) is for local builds only. The CI does not use this path.
- The **image** ships its own `[local-aur]` at `file:///opt/localrepo` (see `.live-pacman.conf`). It holds `localrepo/` (copied by CI/`update.sh`) plus everything `install-deps` built. pacstrap runs without `-P`, so the target keeps the stock `/etc/pacman.conf`; `alvin-install` strips `[local-aur]` from it anyway as a safety net, because `/opt/localrepo` exists only on the medium.
- `alvin-install` runs end-4 with `--skip-alldeps` — dependencies must come from the ISO. It feeds `yes i` to end-4's failure prompt: inside the installer chroot the medium's `/run` is bind-mounted in, so end-4's `systemctl --machine=…@.host --user enable … --now` lines can never work (and `--now` would start services on the live medium). `install_end4_dots` re-applies the enable-only part (bluetooth, ydotool, i2c module) outside the chroot and reports every step end-4 had to skip. If the end-4 step reports "incomplete", check that `hyprctl`/`quickshell`/the venv were resolvable (harvest) before re-running anything.
- `/etc/skel/.cache/uv` + `/etc/skel/.local/share/uv/python` are pre-warmed by the build (~460 MB: CPython 3.12 and the pinned wheel cache) so end-4's `uv venv`/`uv pip install` at install time needs no network.
- **end4-pC** (`pctrade/end4-pC`) is the default quickshell config: cloned into `/etc/skel/.config/quickshell/end4-pC` at build time, selected by patching `hl.env("qsConfig", "ii")` → `"end4-pC"` in the **staged** `/opt/dots-hyprland/dots/.config/hypr/hyprland/variables.lua` (there is no `hypr/hyprland/` in this repo — `./setup install` copies it to the installed system). `alvin-install` re-copies end4-pC into the user's home *after* `./setup install`, because end-4's installer `rsync --delete`s all of `~/.config/quickshell`.
- `airootfs/etc/skel/.config/illogical-impulse/presets/` carries the user's presets (`Laptop.json`, `Personal-Performance.json`, `Personal-Performance.zip`). They embed `/home/alvin/...` paths, so wallpapers/screenshots need fixing up after install.
- End-4 metapackages are built from `localrepo/end4-pkgbuilds/` (quickshell-git, bibata-modern-classic-bin, microtex-git).
- **quickshell pin / Qt 6.12**: end-4 pins quickshell to `41651d7` (Sep 25), which predates upstream fix `5d5d498` (Oct 3, *"use MOC includes instead of opaque pointer definitions — … Qt 6.12 compatibility"*). Arch shipped qt6 6.12.0 on Oct 8 and the old pin died with `Meta Types must be fully defined` in the moc build of quickshell-hyprland-ipc (failed CI run 137). `customize_airootfs.sh` therefore rewrites end-4's `_commit` to `11ca60be` after cloning — but **only** while it still matches `^_commit='41651d7`, so once end-4 bumps their own pin the guard goes inert and theirs wins. `localrepo/check-quickshell-pin.sh` (weekly `pin-check.yaml` job) fails CI if either pin goes stale or loses the fix; it warns instead of failing when end-4's known-stale pin is covered by the override.
- **MicroTeX flags**: the image's `makepkg.conf` is CachyOS's (`-march=native -O3`, LTO). GCC 16.2 hit an internal compiler error (segfault) on MicroTeX's `gtkmm_main.cpp` with it (failed CI run 155; not reproducible locally), so `customize_airootfs.sh` patches end-4's `illogical-impulse-microtex-git` PKGBUILD to `options=(!lto)` + `-O2` (our `localrepo/end4-pkgbuilds/microtex-git` carries the same).
- **CAP_SETFCAP on the runner**: without it pacstrap drops `security.capability` xattrs (newuidmap/newgidmap, snap-confine, dumpcap, kwin_wayland, ...) from the live ISO with only a warning. `build-iso.yaml` fails the job early if the runner lacks it — fix the runner, don't remove the check.
- **yay SIGSEGV on exit**: yay 13 (libalpm via purego/`dyalpm`) can segfault in `alpm_release()` *after* a finished transaction (failed CI run at end-4's `illogical-impulse-kde` step), which aborted `install-deps`. `customize_airootfs.sh` puts a temporary `yay` shim first on the builder's `PATH`: a failed plain `yay -S <pkgs>` counts as success only if `pacman -T` confirms every target is installed (it logs `[yay-shim]`). Every other failure keeps yay's exit code.
- mono's `cert-sync` runs before `ca-certificates.crt` exists during pacstrap, so `customize_airootfs.sh` (live) and `install_pacstrap` (target) re-run it.
- `/etc/skel/.zshrc` is owned by `grml-zsh-config`, so ours is staged as `airootfs/root/.zshrc-skel` and installed by `customize_airootfs.sh` after pacstrap — never add files under `airootfs/` at paths a package owns, or pacstrap aborts with "exists in filesystem".
- `packages.x86_64` is sorted alphabetically. Keep it sorted when adding/removing entries.
- `airootfs/opt/localrepo/` is a build artifact (gitignored) — never commit it.
- **Build stamp flow**: `profiledef.sh` (sourced by mkarchiso) stages the gitignored `airootfs/root/.iso-build-stamp` when a `.git` dir exists **or** `CI`/`GITEA_ACTIONS` is set — line reads `AlvinArch build <date> | commit <date> | <4-char hash>` (falls back to `commit unknown` if `git -c safe.directory` can't read the repo; without `.git`/CI no file is staged). `customize_airootfs.sh` keeps it as `/etc/alvin-build-stamp` on the live medium **and appends it to the live `/etc/motd`**, so the booted ISO identifies its build. The installed system's motd is never touched by us — `alvin-install` has no motd step (an earlier `install_motd` that copied the banner + stamp into the target was removed on purpose).
Symlink
+1
View File
@@ -0,0 +1 @@
AGENTS.md
+119 -8
View File
@@ -21,7 +21,6 @@ AlvinArch/
├── localrepo/
│ ├── build-end4-deps.sh # non-interactive AUR build (all deps)
│ ├── end4-pkgbuilds/ # microtex-git, quickshell-git, bibata
│ ├── illogical-impulse-quickshell-git-*.pkg.tar.zst
│ ├── illogical-impulse-bibata-modern-classic-bin-*.pkg.tar.zst
│ └── update.sh # existing, regenerates repo db
└── packages.x86_64 # 420 packages (65+ end-4 deps added)
@@ -37,9 +36,10 @@ AlvinArch/
6. [System Services & Networking](#system-services--networking)
7. [Local Repository (localrepo/)](#local-repository-localrepo)
8. [Boot Configuration](#boot-configuration)
9. [Build & Test](#build--test)
10. [Planned Additions](#planned-additions)
11. [Troubleshooting](#troubleshooting)
9. [Automated Installer (alvin-install)](#automated-installer-alvin-install)
10. [Build & Test](#build--test)
11. [Planned Additions](#planned-additions)
12. [Troubleshooting](#troubleshooting)
---
@@ -50,6 +50,7 @@ AlvinArch/
### Key Features
- **Hyprland + end-4** — Wayland compositor with illogical-impulse dotfiles (quickshell, bibata cursors, microtex)
- **Automated installer (`alvin-install`)** — gum TUI: LUKS with systemd initramfs or experimental touchscreen unl0kr, optional TPM2 auto-unlock, GitHub SSH key import, login manager choice (none/greetd/sddm/gdm), optional sshd (on by default), autologin; the build stamp identifies the live ISO via its `/etc/motd`
- **LazyVim** — Pre-configured Neovim with lazy.nvim and curated plugins
- **Fish + Zsh + Starship** — Dual shell setup with oh-my-zsh and a cross-shell prompt
- **Local AUR repo** — Pre-built packages (end-4 deps, wallust, grub-customizer, etc.) that aren't in official repos
@@ -137,7 +138,7 @@ AlvinArch/
│ │ └── mkinitcpio.conf.d/ # archiso initramfs config
│ ├── usr/
│ │ ├── lib/os-release # usr-merge os-release
│ │ ├── local/bin/ # choose-mirror, Installation_guide, livecd-sound
│ │ ├── local/bin/ # alvin-install, choose-mirror, Installation_guide, livecd-sound
│ │ ├── local/share/livecd-sound/ # ALSA config (asound.conf.in)
│ │ └── share/pacman/keyrings/ # CachyOS GPG keyring + trust data
│ └── root/
@@ -151,7 +152,7 @@ AlvinArch/
│ ├── update.sh # Regenerates repo DB + syncs names to packages.x86_64
│ ├── end4-pkgbuilds/ # PKGBUILDs: quickshell-git, microtex-git, bibata-modern-classic-bin
│ ├── *.pkg.tar.zst # Pre-built packages (illogical-impulse-*, wallust, grub-customizer,
│ │ # snapd, upscayl, meshy-git, pacserve, etc.)
│ │ # snapd, upscayl, eternalterminal, etc.)
│ └── local-aur.db{,.tar.gz} # Repo database files
├── efiboot/ # UEFI boot (systemd-boot)
│ └── loader/
@@ -229,7 +230,7 @@ Cross-shell prompt config for [Starship](https://starship.rs/). Works with both
### **.zshrc / .fzf.zsh**
Zsh configuration adapted for oh-my-zsh (installed by `customize_airootfs.sh`) and fzf shell integration.
Zsh configuration adapted for oh-my-zsh (installed by `customize_airootfs.sh`) and fzf shell integration. fish is the default login shell; zsh is there if you `chsh` to it. The `.zshrc` source lives at `airootfs/root/.zshrc-skel`, because grml-zsh-config owns `/etc/skel/.zshrc` and shipping it under `airootfs/etc/skel/` would abort the build. zsh-autocomplete is cloned but disabled; see [Troubleshooting](#zsh-autocomplete-partial-load-error).
---
@@ -284,7 +285,7 @@ Custom PKGBUILDs for end-4 metapackages:
### Pre-built Packages
Packages like `illogical-impulse-quickshell-git`, `illogical-impulse-bibata-modern-classic-bin`, `wallust`, `grub-customizer`, `snapd`, `upscayl-bin`, `meshy-git`, `xwaylandvideobridge`, `pacserve`, etc.
Packages like `illogical-impulse-bibata-modern-classic-bin`, `wallust`, `grub-customizer`, `snapd`, `upscayl-bin`, `xwaylandvideobridge`, `eternalterminal`, etc.
---
@@ -304,6 +305,66 @@ Legacy BIOS boot with custom splash screen. Includes PXE network boot config.
---
## Automated Installer (alvin-install)
The ISO ships an interactive installer at `/usr/local/bin/alvin-install` (source:
`airootfs/usr/local/bin/alvin-install`, ~1,880 lines of bash; the live `/etc/motd`
advertises it). It partitions, encrypts, installs, and fully configures a system —
including the end-4 dotfiles — without network access to this repo.
### Running it
```bash
alvin-install # as root on the live medium (root autologins on tty1)
```
Requirements: **UEFI boot** (it exits if `/sys/firmware/efi` is missing — BIOS/CSM
is not supported by the installer) and `gum`. A network connection is required and
verified by pinging `archlinux.org` before any disk is touched; the network screen
can open `nmtui` for WiFi. Every step is logged to `/var/log/alvin-install.log`,
which is copied into the installed system.
### What it asks (gum TUI screens)
Keymap → network → target disk (the live USB is hidden from the list) → LUKS
encryption (optional; **the LUKS passphrase is the same as your login password**)
→ experimental unl0kr touchscreen unlock → filesystem (`ext4`/`btrfs`) →
username/password/hostname → git identity + GitHub username (both optional) →
timezone/locale → login manager → SSH server (enabled by default) → summary.
### What it does
1. GPT partitioning (1 GiB ESP) on the chosen disk, optional LUKS2 on the rest,
format + mount at `/mnt`
2. `pacstrap` with the package list derived from the medium's
`pkglist.x86_64.txt` (`pacman -Q` of the live ISO) — `archiso`, `syslinux`,
`memtest86+` etc. are dropped, hardcoded essentials (kernel, GRUB,
NetworkManager, cryptsetup, microcode) always appended
3. User account, sudo, git config, GitHub SSH keys (`ssh-import-id`, falling
back to `github.com/<user>.keys`)
4. end-4 dotfiles from the staged `/opt/dots-hyprland` via
`./setup install -f -s --skip-alldeps`
5. Initramfs in one of two modes: **Mode A** — systemd initramfs + `sd-encrypt`
(default); **Mode B** — busybox hooks + unl0kr touchscreen unlock
(experimental, mutually exclusive with TPM2; falls back to Mode A on failure)
6. Optional **TPM2 auto-unlock** (`systemd-cryptenroll`, PCR 0+7; passphrase
slot kept as fallback; warns if Secure Boot is off)
7. GRUB (UEFI, boot entry `ALVIN`), login manager — `none` (plain tty, Hyprland
autostarts), `greetd`+tuigreet, `sddm`, or `gdm` — falls back to `none` only
if the **package** can't be resolved/installed; a failed `systemctl enable`
keeps `getty@tty1` as the login path (the summary still reports the chosen
manager). Optional autologin (forced off when TPM2 is enrolled), and sshd
enabled at boot when selected (default: on) — Arch's stock config allows
**password login with the same password as LUKS**, so only accept the default
on networks you trust (or harden `sshd_config` afterwards). The final banner
reports whether the enable actually succeeded, not just what was selected
On failure an `EXIT` trap unmounts `/mnt` and closes the `cryptroot` mapping, so
re-running is safe. If the end-4 step reports "incomplete", see the repository's
`AGENTS.md` Gotchas for the chroot/systemd limitations that cause it.
---
## Build & Test
### Building the ISO
@@ -376,6 +437,56 @@ The following dotfiles are not yet included but are planned for future versions:
- `customize_airootfs.sh` clones repos at build time — requires network access during `mkarchiso`
- Check build logs for git clone failures
### zsh-autocomplete partial-load error
Symptom:
```
_autocomplete__history_lines: function not defined
_autocomplete__unambiguous: function not defined
```
Cause: zsh-autocomplete was loaded after `compinit` (e.g. via oh-my-zsh
`plugins=(...)`), a stale completion dump, or a conflict with
zsh-autosuggestions / zsh-history-substring-search. On this ISO `compinit`
always runs first: grml-zsh-config's global `/etc/zsh/zshrc` calls it
unconditionally before `~/.zshrc` is read.
The shipped `~/.zshrc` leaves zsh-autocomplete out of `plugins=(...)`. The
plugin stays cloned at `~/.oh-my-zsh/custom/plugins/zsh-autocomplete/`
either way, just disabled by default.
**Fix: drop autocomplete (recommended)**
```bash
# 1. Edit ~/.zshrc, remove this line from plugins=(...):
# zsh-autocomplete
# 2. Clear stale dumps (bash avoids zsh's nomatch abort on the glob):
bash -c 'rm -f ~/.zcompdump* ~/.cache/zsh/zcompdump*'
# 3. Restart the shell and verify:
exec zsh
typeset -f _autocomplete__history_lines >/dev/null && echo "still loaded" || echo "clean"
```
**Fix: keep autocomplete instead**
```bash
# 1. Remove zsh-autocomplete, zsh-autosuggestions and
# zsh-history-substring-search from plugins=(...) in ~/.zshrc.
# 2. Stop the global compinit. `skip_global_compinit=1` in ~/.zshenv does
# NOT work here (that is a Debian/Ubuntu /etc/zsh/zshrc convention; grml
# ignores it), so remove the package that ships the global zshrc:
sudo pacman -R grml-zsh-config
# 3. At the very top of ~/.zshrc, before oh-my-zsh is sourced:
source ~/.oh-my-zsh/custom/plugins/zsh-autocomplete/zsh-autocomplete.plugin.zsh
# 4. Clear dumps as above, then: exec zsh
```
---
## Acknowledgements
@@ -0,0 +1,11 @@
# mkinitcpio preset file for the 'linux-cachyos' package on archiso
# (the kernel the boot entries load). Not owned by any package: mkinitcpio's
# install hook only generates a preset when none exists, so this one wins and
# the image gets the archiso initramfs alone - no unused fallback image.
PRESETS=('archiso')
ALL_kver='/boot/vmlinuz-linux-cachyos'
archiso_config='/etc/mkinitcpio.conf.d/archiso.conf'
archiso_image="/boot/initramfs-linux-cachyos.img"
-8
View File
@@ -1,8 +0,0 @@
# mkinitcpio preset file for the 'linux' package on archiso
PRESETS=('archiso')
ALL_kver='/boot/vmlinuz-linux'
archiso_config='/etc/mkinitcpio.conf.d/archiso.conf'
archiso_image="/boot/initramfs-linux.img"
+31
View File
@@ -0,0 +1,31 @@
# ~/.zshrc - installed into /etc/skel by customize_airootfs.sh (it replaces
# grml-zsh-config's skel copy; grml's global /etc/zsh/zshrc still loads first).
export ZSH="$HOME/.oh-my-zsh"
ZSH_THEME="" # starship draws the prompt
zstyle ':omz:update' mode reminder # never auto-pull on startup
# Plugins are pre-cloned into $ZSH/custom/plugins at ISO build time.
# zsh-syntax-highlighting must load before zsh-history-substring-search.
# zsh-autocomplete is cloned too but left off: it must load before compinit,
# which grml's /etc/zsh/zshrc already ran, and it fights zsh-autosuggestions.
# To use it anyway, see README.md "zsh-autocomplete partial-load error".
plugins=(
git
fzf-zsh-plugin
zsh-eza
zsh-autosuggestions
zsh-syntax-highlighting
zsh-history-substring-search
)
source "$ZSH/oh-my-zsh.sh"
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down
[[ -f ~/.fzf.zsh ]] && source ~/.fzf.zsh
if command -v starship >/dev/null 2>&1 && [[ "$TERM" != "linux" ]]; then
eval "$(starship init zsh)"
fi
+115 -10
View File
@@ -1,6 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
# The chroot's /dev is the kernel's single shared devtmpfs, which only gets the
# fd/stdin/stdout/stderr symlinks at boot (from mkinitcpio's init). If the
# runner environment lacks them, process substitution (< <(...)) and upstream
# tools (pacman-key) fail with "… /dev/fd/63: No such file or directory".
# Self-heal here; the CI build-iso workflow does the same before pacstrap so
# that mkinitcpio's hook (which pipes through /dev/stdin) works too.
for _link in "fd:/proc/self/fd" "stdin:/proc/self/fd/0" \
"stdout:/proc/self/fd/1" "stderr:/proc/self/fd/2"; do
[[ -e "/dev/${_link%%:*}" || -L "/dev/${_link%%:*}" ]] ||
ln -sfn "${_link#*:}" "/dev/${_link%%:*}"
done
unset _link
# Populate the keyring in the built root from the keyring bundles
# (/usr/share/pacman/keyrings, incl. the copied cachyos keyring).
# This mirrors the guide; it runs inside the image you are making.
@@ -14,6 +27,17 @@ pacman-key --populate archlinux cachyos chaotic
install -m 0644 /root/.live-pacman.conf /etc/pacman.conf
rm -f /root/.live-pacman.conf
# Build stamp staged by profiledef.sh (absent for non-git, non-CI builds):
# keep the stamp file itself as a reference, drop the staging copy, and
# stamp the live medium's motd so the booted ISO identifies its build (the
# installed system keeps the stock package motd - alvin-install never
# writes /etc/motd).
if [[ -f /root/.iso-build-stamp ]]; then
install -m 0644 /root/.iso-build-stamp /etc/alvin-build-stamp
cat /etc/alvin-build-stamp >> /etc/motd
rm -f /root/.iso-build-stamp
fi
# ============================================================================
# Pre-install dotfile dependencies into /etc/skel so new users get them
# instantly on first login (no network required).
@@ -48,6 +72,14 @@ for plugin in "${!OMZ_PLUGINS[@]}"; do
fi
done
# grml-zsh-config owns /etc/skel/.zshrc, so shipping ours in
# airootfs/etc/skel would make pacstrap abort ("exists in filesystem").
# Stage it under /root and overwrite the package's copy here, after pacstrap.
if [[ -f /root/.zshrc-skel ]]; then
install -m 0644 /root/.zshrc-skel "${SKEL}/.zshrc"
rm -f /root/.zshrc-skel
fi
# ── LazyVim (lazy.nvim bootstraps on first launch, but pre-clone the plugin) ─
NVIM_DATA="${SKEL}/.local/share/nvim"
mkdir -p "${NVIM_DATA}"
@@ -65,6 +97,34 @@ if [[ ! -d "${DOTS_REPO}/.git" ]]; then
git -C "${DOTS_REPO}" submodule update --init --depth=1
fi
# end-4 pins quickshell to 41651d7 (Sep 25), which predates quickshell's
# Qt 6.12 fix (5d5d498, Oct 3: "use MOC includes instead of opaque pointer
# definitions - Fixes linking issues and Qt 6.12 compatibility"). Arch
# shipped qt6 6.12.0 into extra on Oct 8; with the old pin qmetatype.h's
# "Meta Types must be fully defined" static_assert kills the moc build of
# quickshell-hyprland-ipc (failed CI run 137) and the whole ISO build with
# it. Force the fixed commit, but ONLY while end-4 still carries the old
# pin - once they bump it themselves the guard stops matching and their
# pin wins untouched. localrepo/check-quickshell-pin.sh (weekly CI)
# verifies both this pin and end-4's stay Qt 6.12-compatible.
QS_PKG="${DOTS_REPO}/sdata/dist-arch/illogical-impulse-quickshell-git/PKGBUILD"
if [[ -f "${QS_PKG}" ]] && grep -q "^_commit='41651d7" "${QS_PKG}"; then
echo "[customize] Bumping end-4 quickshell pin to 11ca60be (Qt 6.12 fix)..."
sed -i "s|^_commit='41651d7[^']*'|_commit='11ca60be22b063478ed9586ca1d7f92f0f261caf'|" "${QS_PKG}"
fi
# GCC 16.2 ICEs (segfault in ggc_set_mark) compiling MicroTeX's gtkmm_main.cpp
# - the LaTeX binary the package ships - under CachyOS's makepkg.conf
# (-march=native -O3 + -flto=auto), which killed CI run 155. Build it with -O2
# and no LTO; MicroTeX is tiny, the flags buy nothing. Re-running is a no-op.
MTX_PKG="${DOTS_REPO}/sdata/dist-arch/illogical-impulse-microtex-git/PKGBUILD"
if [[ -f "${MTX_PKG}" ]] && ! grep -q '^options=(!lto)' "${MTX_PKG}"; then
echo "[customize] Building MicroTeX with -O2 and no LTO (GCC 16 ICE)..."
# shellcheck disable=SC2016 # expanded by makepkg, not here
sed -i '/^build() {/a\ export CFLAGS="${CFLAGS//-O3/-O2}" CXXFLAGS="${CXXFLAGS//-O3/-O2}"' "${MTX_PKG}"
printf '\noptions=(!lto)\n' >> "${MTX_PKG}"
fi
# ── end4-pC: the default quickshell config, shipped in skel ──────────────────
# pctrade/end4-pC is a fork of the ii shell config. It lives next to ii in
# ~/.config/quickshell and does not modify it; which one runs is decided by
@@ -112,6 +172,36 @@ chmod 440 /etc/sudoers.d/99-ii-builder
# so the tree must be owned by the account that runs it.
chown -R "${BUILD_USER}:${BUILD_USER}" "${DOTS_REPO}"
# yay 13 (libalpm via purego/dyalpm) can SIGSEGV in alpm_release() at exit,
# *after* the transaction and its hooks completed (failed CI run: crash in
# dyalpm handle.Release). end-4 sees exit 2 and aborts. This shim, put first
# on PATH for the install-deps run only, turns a failed plain `yay -S <pkgs>`
# into success when every target is installed afterwards (pacman -T); any
# other failure keeps yay's exit code. It lives in /tmp and is removed after.
YAY_SHIM_DIR="$(mktemp -d /tmp/ii-yay-shim.XXXXXX)"
cat > "${YAY_SHIM_DIR}/yay" <<'EOF'
#!/bin/bash
"${YAY_SHIM_REAL:-/usr/bin/yay}" "$@"
rc=$?
(( rc == 0 )) && exit 0
sync=0
targets=()
for a in "$@"; do
case "$a" in
-S|--sync) sync=1 ;;
-S*) exit "$rc" ;; # -Ss/-Si/-Syu/...: not a plain install
-*) ;;
*) targets+=("$a") ;;
esac
done
if (( sync && ${#targets[@]} )) && pacman -T -- "${targets[@]}" >/dev/null; then
echo "[yay-shim] yay exited ${rc}, but all ${#targets[@]} targets are installed - continuing" >&2
exit 0
fi
exit "$rc"
EOF
chmod 755 "${YAY_SHIM_DIR}" "${YAY_SHIM_DIR}/yay"
# -f: never prompt (ask=false); -s: skip "pacman -Syu";
# --skip-plasmaintg: optional Firefox widget that pulls 410 pkgs / 526 MiB of
# KDE packages onto a non-KDE system;
@@ -120,11 +210,14 @@ chown -R "${BUILD_USER}:${BUILD_USER}" "${DOTS_REPO}"
# shellcheck disable=SC2024 # the redirect is meant for the child, not sudo
if ! sudo -u "${BUILD_USER}" env HOME="/home/${BUILD_USER}" \
GIT_TERMINAL_PROMPT=0 \
PATH="${YAY_SHIM_DIR}:/usr/local/sbin:/usr/local/bin:/usr/bin" \
bash -c "cd ${DOTS_REPO} && ./setup install-deps -f -s --skip-plasmaintg" \
< <(yes e); then
rm -rf "${YAY_SHIM_DIR}"
echo "[customize] end-4 ./setup install-deps failed" >&2
exit 1
fi
rm -rf "${YAY_SHIM_DIR}"
# ── Harvest the packages install-deps just built into the in-image local repo ─
# The live /etc/pacman.conf points [local-aur] at /opt/localrepo; without the
@@ -177,11 +270,13 @@ fi
# Rebuild the repo database (CI/update.sh ship a pre-built one alongside the
# packages) and refresh the live system's copy of it. No network needed: the
# remote repos were synced during pacstrap.
# remote repos were synced during pacstrap. No -R: the loop above already
# deleted the replaced archives, so repo-add's own cleanup only logged a
# spurious "Cannot remove file" ERROR.
local_pkgs=(/opt/localrepo/*.pkg.tar.zst)
if ((${#local_pkgs[@]} > 0)); then
echo "[customize] Rebuilding local-aur db (${#local_pkgs[@]} packages)..."
repo-add -R /opt/localrepo/local-aur.db.tar.gz "${local_pkgs[@]}"
repo-add /opt/localrepo/local-aur.db.tar.gz "${local_pkgs[@]}"
mkdir -p /var/lib/pacman/sync
cp -fL /opt/localrepo/local-aur.db /var/lib/pacman/sync/local-aur.db
if [[ -f /opt/localrepo/local-aur.files ]]; then
@@ -189,6 +284,16 @@ if ((${#local_pkgs[@]} > 0)); then
fi
fi
# ── mono certificate store ──────────────────────────────────────────────────
# mono's post_install runs cert-sync during pacstrap, before the
# post-transaction hook has generated ca-certificates.crt, so the store ships
# empty ("Couldn't retrieve the file") and mono apps reject every TLS cert.
if command -v cert-sync &>/dev/null; then
echo "[customize] Populating mono certificate store..."
cert-sync --quiet /etc/ssl/certs/ca-certificates.crt \
|| echo "[customize] WARNING: cert-sync failed, mono store left empty" >&2
fi
# ── makepkg leftovers + throwaway account do not belong in the ISO ──────────
if [[ -d "${DOTS_REPO}/sdata/dist-arch" ]]; then
find "${DOTS_REPO}/sdata/dist-arch" -mindepth 2 -maxdepth 2 -type d \
@@ -314,18 +419,18 @@ else
echo "[customize] uv not found - skipping python/wheel pre-warm."
fi
# ── Fish: ensure default shell is set for the live user ──────────────────────
# The live user (usually 'archlinux' or 'root') gets fish as default shell
# ── Fish: default shell for the live root user ───────────────────────────────
# (alvin-install creates the installed user with fish as well.)
if command -v fish &>/dev/null; then
echo "[customize] Setting fish as default shell for skel..."
echo "[customize] Setting fish as root's shell on the live medium..."
chsh -s /usr/bin/fish root 2>/dev/null || true
fi
# ── Ensure skel directories have correct permissions ─────────────────────────
chmod 700 "${SKEL}/.config" 2>/dev/null || true
find "${SKEL}/.config" -type f -exec chmod 644 {} \; 2>/dev/null || true
find "${SKEL}/.config" -type d -exec chmod 755 {} \; 2>/dev/null || true
find "${SKEL}/.config" -name '*.sh' -exec chmod 755 {} \; 2>/dev/null || true
find "${SKEL}/.local" -type d -exec chmod 755 {} \; 2>/dev/null || true
# Only take away group/other write and make everything readable: forcing
# files to 644 stripped +x from executables that are not *.sh (python/fish
# helpers in the cloned quickshell configs).
chmod -R go-w,a+rX "${SKEL}/.config" "${SKEL}/.local" 2>/dev/null || true
find "${SKEL}/.config" -name '*.sh' -exec chmod 755 {} + 2>/dev/null || true
echo "[customize] Dotfile pre-installation complete."
File diff suppressed because it is too large Load Diff
+25 -19
View File
@@ -59,8 +59,12 @@ build_local_pkgbuild() {
local pkg_name
pkg_name=$(basename "${pkg_dir}")
if ls "${REPO_DIR}"/illogical-impulse-"${pkg_name}"-*.pkg.tar.zst 1>/dev/null 2>&1; then
info "Package already exists in localrepo, skipping: ${pkg_name}"
# Skip only while a built package is newer than its PKGBUILD - bumping
# e.g. quickshell's _commit must trigger a rebuild, not ship the old one.
if [[ -n "$(find "${REPO_DIR}" -maxdepth 1 \
-name "illogical-impulse-${pkg_name}-*.pkg.tar.zst" \
-newer "${pkg_dir}/PKGBUILD" -print -quit)" ]]; then
info "Package up to date in localrepo, skipping: ${pkg_name}"
return 0
fi
@@ -87,34 +91,36 @@ done
info "═══ Step 2: Building AUR dependencies ═══"
# ── Theming & appearance ─────────────────────────────────────────────────────
# Entries in these lists must start at column 0: CI's package check accepts
# repo-less packages via `grep -qxF <pkg>` against this file (exact whole-line
# match), which silently never matched while the names were indented.
AUR_THEMING=(
adw-gtk-theme-git
darkly-bin
ttf-material-symbols-variable-git
ttf-readex-pro
ttf-rubik-vf
ttf-twemoji
otf-space-grotesk
kde-material-you-colors-git
adw-gtk-theme-git
darkly-bin
ttf-material-symbols-variable-git
ttf-readex-pro
ttf-rubik-vf
ttf-twemoji
otf-space-grotesk
kde-material-you-colors-git
)
# ── end-4 specific / build-from-source packages ──────────────────────────────
# matugen — material you color generation tool
# speech-dispatcher — may need AUR build on some setups
AUR_TOOLS=(
matugen
python-rpypostprocess
tts
matugen
python-rpypostprocess
tts
)
# ── Packages not in any standard repo ────────────────────────────────────────
AUR_MISSING=(
asciinema-agg
davs2
syncthingtray-qt6
varia
woeusb-ng
ttf-ms-fonts
asciinema-agg
davs2
varia
woeusb-ng
ttf-ms-fonts
)
# ── Large / complex builds (skipped with --quick) ────────────────────────────
+184
View File
@@ -0,0 +1,184 @@
#!/usr/bin/env bash
# Verify that every quickshell pin we build against is current enough to
# survive the next Arch Qt update.
#
# Why this exists: CI run 137 (2026-10-08) failed because end-4 pinned
# quickshell to 41651d7 (Sep 25), which predates quickshell's Qt 6.12 fix
# (5d5d498, Oct 3: "use MOC includes instead of opaque pointer definitions
# - Fixes linking issues and Qt 6.12 compatibility"). Arch shipped
# qt6 6.12.0 the next day and the pinned commit stopped compiling
# ("Meta Types must be fully defined"). customize_airootfs.sh papers over
# end-4's stale pin at build time; this script is the tripwire that tells
# us when pins - ours OR end-4's - fall behind again BEFORE a build does.
#
# Checked pins:
# 1. localrepo/end4-pkgbuilds/quickshell-git/PKGBUILD (our own pin)
# 2. end-4/dots-hyprland sdata/dist-arch PKGBUILD (live from GitHub)
#
# Checks per pin:
# - commit exists upstream
# - includes the Qt 6.12 fix (5d5d498)
# - not older than QUICKSHELL_PIN_MAX_AGE_DAYS (default 45)
#
# Exit 1 on any failure so CI (pin-check.yaml) goes red.
#
# Usage: ./localrepo/check-quickshell-pin.sh
set -euo pipefail
QT612_FIX='5d5d49873fe8cf1f99ddfd5006ceb2057c5c9b13'
QS_REPO='https://git.outfoxxed.me/quickshell/quickshell.git'
END4_STALE_PIN='41651d7' # the pin customize_airootfs.sh overrides
END4_PKGBUILD_URL='https://raw.githubusercontent.com/end-4/dots-hyprland/main/sdata/dist-arch/illogical-impulse-quickshell-git/PKGBUILD'
MAX_AGE_DAYS="${QUICKSHELL_PIN_MAX_AGE_DAYS:-45}"
profile_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
LOCAL_PKGBUILD="${profile_dir}/localrepo/end4-pkgbuilds/quickshell-git/PKGBUILD"
FAILED=0
fail() {
echo "❌ $1" >&2
FAILED=1
}
# ── collect pins ────────────────────────────────────────────────────────────
pin_of() { # <file-contents-stdin> -> first _commit='...' value
sed -n "s/^_commit='\([0-9a-f]\{7,40\}\)'.*/\1/p" | head -n1
}
if [[ ! -f "$LOCAL_PKGBUILD" ]]; then
echo "❌ missing ${LOCAL_PKGBUILD}" >&2
exit 1
fi
local_pin=$(pin_of < "$LOCAL_PKGBUILD")
if [[ -z "$local_pin" ]]; then
echo "❌ no _commit= found in ${LOCAL_PKGBUILD}" >&2
exit 1
fi
end4_pin=''
end4_pkgbuild=''
for try in 1 2 3 4 5; do
if end4_pkgbuild=$(curl -fsSL --max-time 30 "$END4_PKGBUILD_URL"); then
end4_pin=$(printf '%s\n' "$end4_pkgbuild" | pin_of)
break
fi
echo " ⏳ fetch of end-4 PKGBUILD failed (try $try/5), retrying in $(( try * 3 ))s..." >&2
sleep $(( try * 3 ))
done
if [[ -z "$end4_pin" ]]; then
fail "could not fetch end-4's quickshell PKGBUILD from GitHub — network or path change?"
echo ""
echo "Result: FAILED"
exit 1
fi
# ── clone quickshell (blobless: commits+trees only, fast) ───────────────────
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
cloned=0
for try in 1 2 3; do
if git clone --filter=blob:none --quiet "$QS_REPO" "$tmp/qs" 2>/dev/null; then
cloned=1
break
fi
rm -rf "$tmp/qs"
echo " ⏳ clone of quickshell failed (try $try/3), retrying in $(( try * 5 ))s..." >&2
sleep $(( try * 5 ))
done
if [[ "$cloned" -ne 1 ]]; then
fail "could not clone ${QS_REPO} — network or repo moved?"
echo ""
echo "Result: FAILED"
exit 1
fi
QS="$tmp/qs"
qs_head=$(git -C "$QS" rev-parse HEAD)
qs_head_date=$(git -C "$QS" show -s --format=%cs HEAD)
echo "quickshell upstream master: ${qs_head:0:12} (${qs_head_date})"
echo ""
now=$(date +%s)
# <label> <pin> <mode>: mode "hard" fails on any problem; mode "soft" only
# warns, for pins we don't control (end-4's) — UNLESS the problem is one the
# customize_airootfs.sh override does not cover, which always fails hard
# because it would break the next ISO build.
check_pin() {
local label=$1 pin=$2 mode=$3
local ts age behind bad=0
if ! git -C "$QS" cat-file -e "${pin}^{commit}" 2>/dev/null; then
fail "$label: pin '$pin' does not exist in quickshell upstream"
return
fi
local has_fix=0
if git -C "$QS" merge-base --is-ancestor "$QT612_FIX" "$pin"; then
has_fix=1
fi
ts=$(git -C "$QS" show -s --format=%ct "$pin")
age=$(( (now - ts) / 86400 ))
behind=$(git -C "$QS" rev-list --count "$pin..HEAD")
if [[ "$mode" == "hard" ]]; then
if [[ "$has_fix" -eq 0 ]]; then
fail "$label: pin '$pin' PREDATES the Qt 6.12 fix (${QT612_FIX:0:12}) — will not compile against Qt >= 6.12"
bad=1
fi
if (( age > MAX_AGE_DAYS )); then
fail "$label: pin is $age days old (> ${MAX_AGE_DAYS}d), $behind commit(s) behind upstream master — bump it"
bad=1
fi
if [[ "$bad" -eq 0 ]]; then
echo " ✅ $label: pin ${pin:0:12} includes the Qt 6.12 fix, age ${age}d (${behind} commit(s) behind master)"
fi
return
fi
# soft mode: our build-time override compensates for this pin, so drift is
# a heads-up — it cannot break the ISO build on its own.
if [[ "$has_fix" -eq 0 ]]; then
echo " ⚠️ $label: pin ${pin:0:12} predates the Qt 6.12 fix — covered by the customize_airootfs.sh override, but end-4 should bump it"
else
echo " ✅ $label: pin ${pin:0:12} includes the Qt 6.12 fix"
fi
if (( age > MAX_AGE_DAYS )); then
echo " ⚠️ $label: pin is $age days old (> ${MAX_AGE_DAYS}d), $behind commit(s) behind master — consider bumping (not enforced, we don't own this pin)"
else
echo " ✅ $label: pin age ${age}d (${behind} commit(s) behind master), limit ${MAX_AGE_DAYS}d"
fi
}
# ── our pin ─────────────────────────────────────────────────────────────────
echo "our pin (localrepo/end4-pkgbuilds):"
check_pin "ours" "$local_pin" hard
# ── end-4's pin ─────────────────────────────────────────────────────────────
echo ""
echo "end-4's pin (dots-hyprland sdata/dist-arch):"
# end-4's pin is only safe if OUR override covers it: customize_airootfs.sh
# rewrites exactly the 41651d7 pin. Any other Qt-unsafe end-4 pin sails
# straight into the ISO build unpatched → hard failure.
if [[ "$end4_pin" == "$END4_STALE_PIN"* ]]; then
echo " ℹ️ end-4 still pins ${end4_pin:0:12} — customize_airootfs.sh override is active"
check_pin "end-4" "$end4_pin" soft
else
check_pin "end-4" "$end4_pin" hard
echo " ℹ️ end-4 has moved off ${END4_STALE_PIN} to ${end4_pin:0:12}"
echo " → if it now passes, consider retiring the override guard in"
echo " customize_airootfs.sh and syncing our own pin to match."
fi
echo ""
if [[ "$FAILED" -ne 0 ]]; then
echo "Result: FAILED"
exit 1
fi
echo "Result: OK — all quickshell pins are Qt 6.12-safe and within ${MAX_AGE_DAYS}d of upstream"
@@ -18,6 +18,8 @@ depends=(
makedepends=("git" "cmake")
source=("git+${url}.git")
sha256sums=("SKIP")
# -O3 + LTO (CachyOS makepkg.conf) make GCC 16.2 ICE on gtkmm_main.cpp
options=(!lto)
prepare() {
cd $_pkgname
@@ -26,6 +28,7 @@ prepare() {
}
build() {
export CFLAGS="${CFLAGS//-O3/-O2}" CXXFLAGS="${CXXFLAGS//-O3/-O2}"
cd $_pkgname
cmake -B build -S . -DCMAKE_BUILD_TYPE=None
cmake --build build
@@ -1,4 +1,4 @@
_commit='7511545ee20664e3b8b8d3322c0ffe7567c56f7a'
_commit='11ca60be22b063478ed9586ca1d7f92f0f261caf'
# Useful links:
# https://git.outfoxxed.me/quickshell/quickshell/commits/branch/master
# https://aur.archlinux.org/packages/quickshell-git
@@ -8,7 +8,7 @@ _prefix='illogical-impulse'
_pkgname=quickshell
pkgname="$_prefix-$_pkgname-git"
pkgver=0.1.0.r1
pkgrel=8
pkgrel=9
pkgdesc="$_pkgname-git pinned commit and extra deps for $_prefix"
arch=(x86_64 aarch64)
url='https://git.outfoxxed.me/quickshell/quickshell'
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -39,7 +39,8 @@ rm -f "${img_repo}"/*.pkg.tar.zst "${img_repo}/${repo_name}".db \
"${img_repo}/${repo_name}".db.tar.gz "${img_repo}/${repo_name}".files \
"${img_repo}/${repo_name}".files.tar.gz
cp -f "${pkgs[@]}" "${img_repo}/"
cp -fL "${repo_dir}/${repo_name}".db* "${repo_dir}/${repo_name}".files* "${img_repo}/"
# explicit names: a .db* glob would also ship repo-add's *.tar.gz.old backups
cp -fL "${repo_dir}/${repo_name}".{db,db.tar.gz,files,files.tar.gz} "${img_repo}/"
echo "Medium copy ready: ${img_repo}"
new_names=()
+40 -24
View File
@@ -3,8 +3,8 @@ alsa-utils
amd-ucode
ansible
archinstall
archlinux-keyring
arch-install-scripts
archlinux-keyring
asciinema
asciinema-agg
ast-grep
@@ -24,20 +24,21 @@ bolt
brightnessctl
brltty
btop
bullet
btrfs-progs
bullet
bully
cachyos-keyring
cachyos-mirrorlist
catppuccin-cursors-macchiato
cava
celt
chaotic-keyring
chaotic-mirrorlist
cifs-utils
clamav
cliphist
clonezilla
cloud-init
cliphist
cmake
cmatrix
code
@@ -46,16 +47,17 @@ cpio
cronie
cryptsetup
cups
curl
darkhttpd
davs2
dconf-editor
ddcutil
ddrescue
dhcpcd
dialog
diffutils
dmidecode
dmraid
ddcutil
dnsmasq
dolphin-plugins
dosfstools
@@ -67,14 +69,12 @@ efibootmgr
electron
element-desktop
espeakup
eternalterminal
ethtool
exfatprogs
f2fs-tools
faac
faad2
fish
flac
fontconfig
fastfetch
fatresize
fcitx5
@@ -87,8 +87,11 @@ ffmpegthumbnailer
ffmpegthumbs
figlet
firefox
fish
flac
flatpak
foliate
fontconfig
foot-terminfo
foremost
fortune-mod
@@ -96,22 +99,23 @@ fprintd
fsarchiver
fuse-overlayfs
fzf
geoclue
ghostty
ghostty-terminfo
git
geoclue
glib2-devel
gnome-disk-utility
gnome-font-viewer
gnome-online-accounts
gnome-power-manager
go-yq
gpart
gparted
go-yq
gpm
gptfdisk
grml-zsh-config
grub
grub-customizer
gst-plugins-base
gst-plugins-ugly
gst-plugin-va
@@ -129,11 +133,12 @@ htop
hyperv
hypridle
hyprlock
hyprpolkitagent
hyprpicker
hyprpolkitagent
hyprshot
hyprsunset
icu
illogical-impulse-bibata-modern-classic-bin
intel-ucode
inxi
irssi
@@ -162,18 +167,18 @@ kwalletmanager
lazygit
ldns
less
lftp
libass
libdbusmenu-gtk3
libdecor
libevdev
libportal
libportal-gtk4
libqalculate
libpipewire
lftp
libfido2
libfprint
libisoburn
libpipewire
libportal
libportal-gtk4
libqalculate
libtorrent
libusb-compat
linux-atm
@@ -212,14 +217,14 @@ mplayer
mpv-mpris
mtools
nano
ncurses
nbd
ncurses
ndisc6
neofetch
neovim
net-tools
network-manager-applet
networkmanager
network-manager-applet
nfs-utils
nikto
nilfs-utils
@@ -266,22 +271,23 @@ ppp
pptpclient
pv
pwgen
python-aiohttp
python-distro
python-hatchling
python-installer
python-packaging
python-pip
python-pyqt6
python-pyquery
python-ufonormalizer
python-uv
python-virtualenv
python-aiohttp
python-packaging
qalculate-gtk
qemu-full
qemu-guest-agent
qt5-quickcontrols2
qt6ct
qt6-5compat
qt6ct
qt6-imageformats
qt6-multimedia
qt6-positioning
@@ -294,6 +300,7 @@ qt6-wayland
rclone
refind
reflector
rpi-imager-git
rsync
rxvt-unicode-terminfo
samba
@@ -311,17 +318,19 @@ shelly
shelly-flatpak-backend
shfmt
smartmontools
snapd
sof-firmware
sox
songrec
sox
speech-dispatcher
squashfs-tools
sshfs
ssh-import-id
starship
sudo
swaync
syncthing
syncthingtray-qt6
syncthingtray
syntax-highlighting
syslinux
systemd-resolvconf
@@ -356,6 +365,7 @@ ttf-material-symbols-variable-git
ttf-ms-fonts
ttf-readex-pro
ttf-twemoji
ttf-victor-mono
tty-clock
tumbler
udftools
@@ -363,6 +373,7 @@ uget
umockdev
unrar
upower
upscayl-bin
uriparser
usbguard
usb_modeswitch
@@ -376,6 +387,10 @@ vim
virt-manager
virtualbox-guest-utils-nox
vpnc
vulkan-intel
vulkan-nouveau
vulkan-radeon
wallust
wayland-protocols
wine
winetricks
@@ -383,15 +398,16 @@ wireless-regdb
wireless_tools
woeusb-ng
wpa_supplicant
wvdial
wtype
wvdial
xarchiver
xclip
xdg-utils
xdg-user-dirs
xdg-utils
xdotool
xfsprogs
xl2tpd
xwaylandvideobridge
yay
ydotool
yt-dlp
-2
View File
@@ -76,7 +76,6 @@ LocalFileSigLevel = Optional
#Include = /etc/pacman.d/mirrorlist
[cachyos]
SigLevel = Optional TrustAll
Include = /etc/pacman.d/cachyos-mirrorlist
[core]
@@ -98,7 +97,6 @@ Include = /etc/pacman.d/mirrorlist
Include = /etc/pacman.d/mirrorlist
[chaotic-aur]
SigLevel = Optional TrustAll
Include = /etc/pacman.d/chaotic-mirrorlist
[local-aur]
+30
View File
@@ -24,3 +24,33 @@ file_permissions=(
["/usr/local/bin/Installation_guide"]="0:0:755"
["/usr/local/bin/livecd-sound"]="0:0:755"
)
# ── Build stamp (staged file, never committed) ────────────────────────────────
# airootfs/root/.iso-build-stamp lands at /root/.iso-build-stamp on the ISO;
# customize_airootfs.sh keeps it as /etc/alvin-build-stamp and appends it to
# the live medium's /etc/motd (the installed system's motd is left alone).
# Only stamp builds from a git
# checkout or CI — a random source-tarball build would record a bogus commit.
# Sourced by mkarchiso under `set -eu`, hence every expansion is defaulted.
_stamp_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_stamp_file="${_stamp_dir}/airootfs/root/.iso-build-stamp"
rm -f "${_stamp_file}"
if [[ -e "${_stamp_dir}/.git" || -n "${CI:-}" || -n "${GITEA_ACTIONS:-}" ]]; then
_stamp_build="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y-%m-%d)"
# git ≥2.46 honours -c safe.directory even when mkarchiso runs as root
# over a user-owned checkout; older gits ignore it (protected-config
# scope) and fall through to the "unknown" line below instead of failing
# the build (`|| true` keeps set -e happy either way).
_stamp_commit="$(git -C "${_stamp_dir}" -c safe.directory="${_stamp_dir}" \
log -1 --format='%cs|%h' 2>/dev/null || true)"
if [[ -n "${_stamp_commit}" && "${_stamp_commit}" == *"|"* ]]; then
_stamp_hash="${_stamp_commit##*|}"
printf 'AlvinArch build %s | commit %s | %s\n' \
"${_stamp_build}" "${_stamp_commit%%|*}" "${_stamp_hash:0:4}" \
> "${_stamp_file}"
else
printf 'AlvinArch build %s | commit unknown\n' \
"${_stamp_build}" > "${_stamp_file}"
fi
fi
unset _stamp_dir _stamp_file _stamp_build _stamp_commit _stamp_hash