Tests / tests (push) Successful in 2m56s
unl0kr already prompts for the LUKS passphrase on screen, so a login manager on top of it would be a second prompt. The login screen now only collects a fallback (none/greetd/sddm/gdm) when unl0kr is selected; resolve_login_manager applies it only when install_unl0kr falls back to the systemd initramfs, and the summary shows both states. Also ships /etc/unl0kr.conf (breezy-dark) plus the empty /usr/share/unl0kr/unl0kr.conf the package omits so unl0kr stops warning about an unparseable config, and pins Hyprland in the user's AccountsService record so gdm autologin does not fall back to gnome.desktop.
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). 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`.
|
|
- `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).
|