The fork's colour/wallpaper/kvantum scripts still hard-code QUICKSHELL_CONFIG_NAME="ii", so they read and write ~/.config/quickshell/ii instead of end4-pC - rewrite them to end4-pC at build time (verified against the current fork HEAD: 5 scripts match the anchored pattern, re-runs are no-ops). Also replace the fork's assets/images/default_wallpaper.png - fed to switchwall.sh by FirstRunExperience.qml on first login - with the wallpaper now shipped in skel's ~/Pictures/Wallpapers, so the first login lands on the japan-street wallpaper instead of the fork's default.
132 lines
14 KiB
Markdown
132 lines
14 KiB
Markdown
# AGENTS.md
|
|
|
|
Arch Linux ISO profile (archiso). Builds a live ISO with Hyprland desktop, end-4 dots-hyprland, and curated packages. Hosted on Gitea.
|
|
|
|
## Build & Test Commands
|
|
|
|
```bash
|
|
# Build the ISO (repo root IS the profile directory)
|
|
sudo mkarchiso -v -w work -o out/ .
|
|
|
|
# Build AUR deps for end-4 (run locally before ISO build if localrepo/ is stale)
|
|
./localrepo/build-end4-deps.sh # build all
|
|
./localrepo/build-end4-deps.sh --quick # skip large AUR pkgs
|
|
|
|
# Refresh localrepo DB after adding/moving .pkg.tar.zst files
|
|
./localrepo/update.sh # re-indexes existing packages
|
|
./localrepo/update.sh pkg1.pkg.tar.zst # add files, then re-index
|
|
```
|
|
|
|
`update.sh` also refreshes `airootfs/opt/localrepo/` (the copy that ships on the
|
|
ISO), so run it after touching `localrepo/` before building.
|
|
|
|
## CI Checks (must pass before merge)
|
|
|
|
- **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`
|
|
|
|
## Package Management
|
|
|
|
**Three sources** contribute packages to the ISO:
|
|
|
|
1. **`packages.x86_64`** — main list, installed by mkarchiso. Must be in an allowed repo.
|
|
2. **`localrepo/*.pkg.tar.zst`** — pre-built AUR packages. The `local-aur` repo is configured in `pacman.conf` and picked up by mkarchiso automatically.
|
|
3. **`build-end4-deps.sh`** — builds end-4 AUR deps at build time.
|
|
|
|
There is a **fourth source**: `customize_airootfs.sh` runs end-4's own
|
|
`./setup install-deps` inside the image (as a throwaway `ii-builder` account),
|
|
which resolves and compiles everything the dotfiles need (quickshell, MicroTeX,
|
|
theming, fonts, ...). Those packages are installed in the live system, land in
|
|
`pkglist.x86_64.txt`, and are harvested into `/opt/localrepo` so pacstrap can
|
|
install them into the target without the network.
|
|
|
|
**Important**: `localrepo/update.sh` auto-appends new package names to `packages.x86_64`. If you remove a package from `packages.x86_64` that exists in `localrepo/`, it will be re-added on the next `update.sh` run.
|
|
|
|
## Key Files
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `packages.x86_64` | Live ISO package list (~402 packages) |
|
|
| `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`); also what pacstrap resolves the target from, so `[cachyos]` must stay ahead of `[core]` like in `pacman.conf` |
|
|
| `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 (none if unl0kr succeeded; with unl0kr selected the login screen only asks for a fallback) → autologin → update (reflector + keyrings + `pacman -Syu`, warn-only) → cleanup, which offers an optional `arch-chroot` shell into the target before unmounting; the `EXIT` trap offers the same shell when a step after pacstrap fails or the install is interrupted (INT/TERM re-exit with 128+N so signals count as failure). 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.
|
|
|
|
A **pre-commit hook** (`githooks/pre-commit`) runs the same ShellCheck pass on every
|
|
commit, so CI failures surface before you push. Enable it once per clone:
|
|
|
|
```bash
|
|
git config core.hooksPath githooks
|
|
```
|
|
|
|
It skips linting (with a warning) when `shellcheck` is missing rather than blocking the
|
|
commit; CI remains the enforcer. Bypass it with `git commit --no-verify`.
|
|
|
|
## Gotchas
|
|
|
|
- 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 **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`. The build also rewrites the fork's hard-coded `QUICKSHELL_CONFIG_NAME="ii"` in its colour/wallpaper/kvantum scripts to `end4-pC` (otherwise they act on `~/.config/quickshell/ii`), and overwrites its `assets/images/default_wallpaper.png` (applied by `FirstRunExperience.qml` on first login) with `airootfs/etc/skel/Pictures/Wallpapers/Dynamic-Wallpapers/Dark/japan-street-Dark.png`.
|
|
- `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.
|
|
- **gvfs / dead `~/.gvfs`**: `install_end4_dots` replays end-4's gsettings writes via `dbus-run-session` as the target user. A session bus D-Bus-activates gvfsd → gvfsd-fuse mounts `~/.gvfs` and dies with the bus, leaving a dead FUSE mount that root can't stat, so the home's catch-all `chown -R` failed (`cannot access '/mnt/home/<user>/.gvfs': Permission denied`) and aborted the install before the bootloader. The replay therefore runs with `GIO_USE_VFS=local GVFS_DISABLE_FUSE=1`, and the function unmounts anything mounted under the home before that chown — keep both.
|
|
- 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).
|