name: Tests on: push: 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 steps: - name: Checkout repo uses: actions/checkout@v4 - name: Reset pacman.conf to stock run: | cat > /etc/pacman.conf << 'STOCKCONF' [options] DisableSandbox HoldPkg = pacman glibc Architecture = auto CheckSpace ParallelDownloads = 5 SigLevel = Required DatabaseOptional LocalFileSigLevel = Optional [core] Include = /etc/pacman.d/mirrorlist [extra] Include = /etc/pacman.d/mirrorlist STOCKCONF sed -i 's/^ //' /etc/pacman.conf # Remove stale sync databases for custom repos rm -f /var/lib/pacman/sync/cachyos* /var/lib/pacman/sync/chaotic* - name: Install prerequisites 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 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 # 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: | pacman -U --noconfirm \ 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst' \ 'https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst' printf '\n[chaotic-aur]\nInclude = /etc/pacman.d/chaotic-mirrorlist\n' \ >> /etc/pacman.conf # 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 bash scripts" echo "═══════════════════════════════════════════════════════════════" # *.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 fi FAILED=0 while IFS= read -r f; do echo "" echo "── Checking: $f" if shellcheck -x -s bash "$f"; then echo " ✅ $f passed" else echo " ❌ $f failed" FAILED=1 fi done <<< "$SH_FILES" if [ "$FAILED" -ne 0 ]; then echo "" echo "═══════════════════════════════════════════════════════════════" echo " ❌ One or more shell scripts have syntax issues" echo "═══════════════════════════════════════════════════════════════" exit 1 fi echo "" echo "═══════════════════════════════════════════════════════════════" echo " ✅ All shell scripts passed ShellCheck" echo "═══════════════════════════════════════════════════════════════" - name: Lint YAML files with yamllint run: | echo "═══════════════════════════════════════════════════════════════" echo " Running yamllint on all YAML files" echo "═══════════════════════════════════════════════════════════════" YAML_FILES=$(find . -not -path './.git/*' \( -name '*.yml' -o -name '*.yaml' \)) if [ -z "$YAML_FILES" ]; then echo "No YAML files found." exit 0 fi FAILED=0 while IFS= read -r f; do echo "" echo "── Checking: $f" if yamllint -d "{extends: relaxed, rules: {document-start: disable, line-length: disable, new-line-at-end-of-file: disable}}" "$f"; then echo " ✅ $f passed" else echo " ❌ $f failed" FAILED=1 fi done <<< "$YAML_FILES" if [ "$FAILED" -ne 0 ]; then echo "" echo "═══════════════════════════════════════════════════════════════" echo " ❌ One or more YAML files have syntax issues" echo "═══════════════════════════════════════════════════════════════" exit 1 fi echo "" echo "═══════════════════════════════════════════════════════════════" echo " ✅ All YAML files passed yamllint" echo "═══════════════════════════════════════════════════════════════" - name: Validate packages.x86_64 syntax run: | echo "═══════════════════════════════════════════════════════════════" echo " Validating packages.x86_64 format" echo "═══════════════════════════════════════════════════════════════" if [ ! -f packages.x86_64 ]; then echo "packages.x86_64 not found, skipping." exit 0 fi FAILED=0 LINENUM=0 while IFS= read -r line; do LINENUM=$((LINENUM + 1)) # Skip empty lines and comments [[ -z "$line" || "$line" =~ ^# ]] && continue # Check for invalid characters if [[ ! "$line" =~ ^[a-zA-Z0-9._@+\-]+$ ]]; then echo "❌ Line $LINENUM: invalid package name format: '$line'" FAILED=1 fi # Check for trailing whitespace if [[ "$line" =~ [[:space:]]$ ]]; then echo "⚠️ Line $LINENUM: trailing whitespace: '$line'" FAILED=1 fi done < packages.x86_64 if [ "$FAILED" -ne 0 ]; then echo "" echo "═══════════════════════════════════════════════════════════════" echo " ❌ packages.x86_64 has syntax issues" echo "═══════════════════════════════════════════════════════════════" exit 1 fi echo "" echo "═══════════════════════════════════════════════════════════════" echo " ✅ packages.x86_64 syntax is valid" echo "═══════════════════════════════════════════════════════════════" - name: Check all packages run: | echo "═══════════════════════════════════════════════════════════════" echo " Checking packages.x86_64 against repos" echo " Allowed repos: cachyos, extra, multilib, chaotic-aur" echo " Also accepted: localrepo/ pre-built packages" echo " Also accepted: packages listed in build-end4-deps.sh" echo "═══════════════════════════════════════════════════════════════" ALLOWED_REPOS="^(core|extra|multilib|cachyos|chaotic-aur)$" MISSING=() FOUND=0 TOTAL=0 while IFS= read -r pkg; do # Skip empty lines and comments [[ -z "$pkg" || "$pkg" =~ ^# ]] && continue TOTAL=$((TOTAL + 1)) # 1) Check allowed repos via pacman repos=$(pacman -Si "$pkg" 2>/dev/null | grep "^Repository" | awk '{print $3}' | tr -d '\r') MATCHED=0 for repo in $repos; do if [[ "$repo" =~ $ALLOWED_REPOS ]]; then MATCHED=1 FOUND=$((FOUND + 1)) break fi done # 2) Check localrepo/ for a pre-built .pkg.tar.zst # Exact name: strip ---.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 if [[ $MATCHED -eq 0 ]] && grep -qxF "${pkg}" localrepo/build-end4-deps.sh 2>/dev/null; then MATCHED=1 FOUND=$((FOUND + 1)) echo "📦 BUILD: $pkg (listed in build-end4-deps.sh)" fi if [[ $MATCHED -eq 0 ]]; then if [[ -z "$repos" ]]; then echo "❌ MISSING: $pkg" else echo "⚠️ WRONG REPO: $pkg (found in: $repos)" fi MISSING+=("$pkg") fi done < packages.x86_64 echo "" echo "═══════════════════════════════════════════════════════════════" echo " Results: $FOUND / $TOTAL packages found in allowed repos" echo "═══════════════════════════════════════════════════════════════" if [[ ${#MISSING[@]} -gt 0 ]]; then echo "" echo "⚠️ ${#MISSING[@]} package(s) NOT in allowed repos:" for p in "${MISSING[@]}"; do echo " • $p" done echo "" echo "These packages must be in:" echo " • cachyos, extra, multilib, or chaotic-aur" echo " • OR pre-built in localrepo/" echo " • OR added to build-end4-deps.sh" exit 1 else echo "" echo "✅ All packages verified successfully!" fi - name: Check for conflicting packages run: | echo "═══════════════════════════════════════════════════════════════" echo " Checking for conflicting packages in packages.x86_64" echo "═══════════════════════════════════════════════════════════════" # 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 if printf '%s\n' "${AVAIL[@]}" | grep -qxF -- "$pkg"; then PKGS+=("$pkg") else echo "⚠️ Skipping unresolvable package: $pkg" fi done < packages.x86_64 # `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 # 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