feat(iso): build end-4 deps in-image, ship end4-pC as default shell
Tests / tests (push) Successful in 2m35s
Tests / tests (push) Successful in 2m35s
- customize_airootfs.sh stages dots-hyprland at /opt, runs ./setup install-deps as a throwaway builder, harvests the built packages into /opt/localrepo so pacstrap resolves them offline - image ships its own [local-aur] (file:///opt/localrepo); CI and localrepo/update.sh keep that copy in sync; .gitignore covers work/, out/ and airootfs/opt/localrepo/ - alvin-install runs ./setup install --skip-alldeps on the target - clone pctrade/end4-pC into /etc/skel/.config/quickshell, patch qsConfig ii -> end4-pC in the staged variables.lua (guarded + idempotent), and restore the fork after setup rsync --delete's ~/.config/quickshell - ship user presets in skel, point fish `q` alias at end4-pC - AGENTS.md: document the fourth package source and new gotchas
This commit is contained in:
@@ -48,4 +48,13 @@ Include = /etc/pacman.d/cachyos-mirrorlist
|
||||
|
||||
[chaotic-aur]
|
||||
SigLevel = Required DatabaseOptional
|
||||
Include = /etc/pacman.d/chaotic-mirrorlist
|
||||
Include = /etc/pacman.d/chaotic-mirrorlist
|
||||
|
||||
[local-aur]
|
||||
# Ships on the medium only: packages built at ISO build time (end-4
|
||||
# dependencies, plus localrepo/). Keeps pacstrap from needing the network for
|
||||
# anything that is not in the official repos. alvin-install strips this whole
|
||||
# section from the target system's pacman.conf, because /opt/localrepo exists
|
||||
# only on the live medium.
|
||||
SigLevel = Optional TrustAll
|
||||
Server = file:///opt/localrepo
|
||||
|
||||
@@ -57,6 +57,181 @@ if [[ ! -d "${NVIM_DATA}/lazy/lazy.nvim" ]]; then
|
||||
https://github.com/folke/lazy.nvim.git "${NVIM_DATA}/lazy/lazy.nvim"
|
||||
fi
|
||||
|
||||
# ── end-4 dots-hyprland, staged for alvin-install (avoids a clone at install) ─
|
||||
DOTS_REPO="/opt/dots-hyprland"
|
||||
if [[ ! -d "${DOTS_REPO}/.git" ]]; then
|
||||
echo "[customize] Cloning end-4 dots-hyprland..."
|
||||
git clone --depth=1 https://github.com/end-4/dots-hyprland.git "${DOTS_REPO}"
|
||||
git -C "${DOTS_REPO}" submodule update --init --depth=1
|
||||
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
|
||||
# qsConfig (patched just below). The repo has no submodules.
|
||||
if [[ ! -d "${SKEL}/.config/quickshell/end4-pC" ]]; then
|
||||
echo "[customize] Cloning end4-pC quickshell config into skel..."
|
||||
git clone --depth=1 https://github.com/pctrade/end4-pC.git \
|
||||
"${SKEL}/.config/quickshell/end4-pC"
|
||||
fi
|
||||
|
||||
# ── Default quickshell config: ii -> end4-pC ─────────────────────────────────
|
||||
# alvin-install copies this staged repo onto the target and runs `./setup
|
||||
# install`, whose rsync writes hypr/hyprland/variables.lua into the user's
|
||||
# home - so patching the staged copy is what sets qsConfig on the installed
|
||||
# system. Re-running is a no-op; anything other than ii/end4-pC aborts.
|
||||
VARIABLES_LUA="${DOTS_REPO}/dots/.config/hypr/hyprland/variables.lua"
|
||||
if grep -Fq 'hl.env("qsConfig", "ii")' "${VARIABLES_LUA}"; then
|
||||
echo "[customize] Setting qsConfig to end4-pC..."
|
||||
sed -i 's|hl\.env("qsConfig", "ii")|hl.env("qsConfig", "end4-pC")|' "${VARIABLES_LUA}"
|
||||
fi
|
||||
if ! grep -Fq 'hl.env("qsConfig", "end4-pC")' "${VARIABLES_LUA}"; then
|
||||
echo "[customize] qsConfig is neither ii nor end4-pC in ${VARIABLES_LUA}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Pre-install end-4 dependencies into the image (network heavy, once per ISO)
|
||||
# ============================================================================
|
||||
# ./setup install-deps resolves and *builds* every AUR/source dependency the
|
||||
# dotfiles need (quickshell, MicroTeX, theming, fonts, ...). Doing it here lets
|
||||
# alvin-install run its end-4 step with --skip-alldeps, so an install only
|
||||
# copies files - no AUR clones, no makepkg, no dependency resolution.
|
||||
#
|
||||
# The script refuses to run as root, hence the throwaway builder account.
|
||||
# ============================================================================
|
||||
BUILD_USER="ii-builder"
|
||||
|
||||
if ! id -u "${BUILD_USER}" &>/dev/null; then
|
||||
useradd -m -u 1500 "${BUILD_USER}"
|
||||
fi
|
||||
echo "${BUILD_USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/99-ii-builder
|
||||
chmod 440 /etc/sudoers.d/99-ii-builder
|
||||
|
||||
# makepkg writes src/, pkg/ and *.pkg.tar.zst into the PKGBUILD directories,
|
||||
# so the tree must be owned by the account that runs it.
|
||||
chown -R "${BUILD_USER}:${BUILD_USER}" "${DOTS_REPO}"
|
||||
|
||||
# -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;
|
||||
# stdin: answer end-4's x() failure prompt with "e", so a failed command
|
||||
# aborts the build instead of blocking forever without a TTY.
|
||||
# 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 \
|
||||
bash -c "cd ${DOTS_REPO} && ./setup install-deps -f -s --skip-plasmaintg" \
|
||||
< <(yes e); then
|
||||
echo "[customize] end-4 ./setup install-deps failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 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
|
||||
# harvested copies pacstrap cannot resolve these packages at install time and
|
||||
# would silently drop them from the target system.
|
||||
mkdir -p /opt/localrepo
|
||||
shopt -s nullglob
|
||||
|
||||
# <name> <version> from a package's embedded .PKGINFO (update.sh does the same)
|
||||
pkg_meta() {
|
||||
tar -xOf "$1" .PKGINFO 2>/dev/null |
|
||||
awk -F' = ' '/^pkgname = /{n=$2} /^pkgver = /{v=$2} END{print n, v}'
|
||||
}
|
||||
|
||||
mapfile -d '' -t built_pkgs < <(
|
||||
find "${DOTS_REPO}" "/home/${BUILD_USER}/.cache/yay" \
|
||||
-name '*.pkg.tar.zst' -print0 2>/dev/null
|
||||
)
|
||||
|
||||
declare -A built_file=() built_ver=()
|
||||
for p in "${built_pkgs[@]}"; do
|
||||
read -r n v < <(pkg_meta "$p")
|
||||
[[ -n "${n:-}" && -n "${v:-}" ]] || continue
|
||||
built_file["$n"]="$p"
|
||||
built_ver["$n"]="$v"
|
||||
done
|
||||
|
||||
# One entry per package name, and the entry must be the version the image
|
||||
# actually installed - a stale copy with the same name but another version
|
||||
# would let pacstrap install something else than what was tested.
|
||||
for existing in /opt/localrepo/*.pkg.tar.zst; do
|
||||
[[ -e "$existing" ]] || continue
|
||||
read -r n v < <(pkg_meta "$existing")
|
||||
[[ -n "${n:-}" ]] || continue
|
||||
if [[ -n "${built_ver[$n]:-}" ]]; then
|
||||
echo "[customize] Replacing localrepo ${n} ${v} with built ${built_ver[$n]}"
|
||||
rm -f "$existing"
|
||||
fi
|
||||
done
|
||||
|
||||
if ((${#built_file[@]} > 0)); then
|
||||
echo "[customize] Harvesting ${#built_file[@]} package(s) into /opt/localrepo"
|
||||
cp -f -- "${built_file[@]}" /opt/localrepo/
|
||||
# makepkg -Afsi leaves its archives next to the PKGBUILD. They are already
|
||||
# harvested (and installed in this image); keeping them in the dots tree
|
||||
# would ship a second copy inside /opt/dots-hyprland, which alvin-install
|
||||
# copies to ~/.dots-hyprland on every installed system.
|
||||
find "${DOTS_REPO}" -type f -name '*.pkg.tar.zst' -delete
|
||||
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.
|
||||
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[@]}"
|
||||
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
|
||||
cp -fL /opt/localrepo/local-aur.files /var/lib/pacman/sync/local-aur.files
|
||||
fi
|
||||
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 \
|
||||
\( -name src -o -name pkg \) -prune -exec rm -rf {} +
|
||||
fi
|
||||
rm -f /etc/sudoers.d/99-ii-builder
|
||||
if ! userdel -r "${BUILD_USER}" 2>/dev/null; then
|
||||
rm -rf "/home/${BUILD_USER:?}"
|
||||
userdel "${BUILD_USER}"
|
||||
fi
|
||||
chown -R root:root "${DOTS_REPO}"
|
||||
|
||||
# ── Google Sans Flex into skel: install_google_sans_flex() early-returns once
|
||||
# fc-list sees the font, so the install step needs no network ──────────────
|
||||
FONT_DIR="${SKEL}/.local/share/fonts/illogical-impulse-google-sans-flex"
|
||||
if [[ ! -f "${FONT_DIR}/.git/HEAD" ]]; then
|
||||
echo "[customize] Pre-installing Google Sans Flex into skel..."
|
||||
rm -rf "${FONT_DIR}"
|
||||
git clone --depth=1 --recurse-submodules --shallow-submodules \
|
||||
https://github.com/end-4/google-sans-flex "${FONT_DIR}"
|
||||
fi
|
||||
|
||||
# ── uv into skel: CPython 3.12 + the end-4 requirements wheel cache ──────────
|
||||
# install-python-packages() runs `uv venv -p 3.12` and `uv pip install -r ...`
|
||||
# during setup. Arch has no python312 package (uv would download one) and the
|
||||
# pinned requirements are ~250 MiB of wheels, so warm both into skel: the venv
|
||||
# is then created and filled straight from the image.
|
||||
if command -v uv &>/dev/null; then
|
||||
echo "[customize] Pre-warming uv (CPython 3.12 + requirements cache)..."
|
||||
warm_venv="/tmp/uv-warm-venv"
|
||||
rm -rf "${warm_venv}"
|
||||
UV_CACHE_DIR="${SKEL}/.cache/uv" \
|
||||
UV_PYTHON_INSTALL_DIR="${SKEL}/.local/share/uv/python" \
|
||||
uv venv -p 3.12 "${warm_venv}"
|
||||
UV_CACHE_DIR="${SKEL}/.cache/uv" \
|
||||
UV_PYTHON_INSTALL_DIR="${SKEL}/.local/share/uv/python" \
|
||||
uv pip install --python "${warm_venv}/bin/python" \
|
||||
-r "${DOTS_REPO}/sdata/uv/requirements.txt"
|
||||
rm -rf "${warm_venv}"
|
||||
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
|
||||
if command -v fish &>/dev/null; then
|
||||
|
||||
Reference in New Issue
Block a user