<feed xmlns='http://www.w3.org/2005/Atom'>
<title>archangel/tests, branch main</title>
<subtitle>Arch Linux installer ISO — ZFS-on-root or BTRFS, doubles as rescue disk
</subtitle>
<id>https://git.cjennings.net/archangel/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/archangel/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/'/>
<updated>2026-06-28T02:08:28+00:00</updated>
<entry>
<title>fix(installer): scope AUR list to filesystem, keep pacman.conf 0644</title>
<updated>2026-06-28T02:08:28+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-28T02:08:28+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=2ead67458add6accda226cca3e21592162d05bfd'/>
<id>urn:sha1:2ead67458add6accda226cca3e21592162d05bfd</id>
<content type='text'>
The baked AUR set installed unconditionally, so zfs-auto-snapshot reached every target. On a btrfs install there's no zfs to satisfy its dependency, and pacstrap aborted the whole transaction. The ISO still bakes the full set. install_base now filters the manifest names through filter_aur_for_fs, dropping zfs-only tooling (zfs-auto-snapshot, zrepl) on a non-zfs target.

strip_repo_stanza mv'd a 0600 mktemp file onto the target, so a clean install shipped /etc/pacman.conf root-only and every user-level makepkg/yay failed to read it. It now truncate-writes through the existing file, preserving the pristine 0644.

Tested in test_common.bats.
</content>
</entry>
<entry>
<title>refactor(installer): extract parse_btrfs_subvol_opts helper</title>
<updated>2026-06-24T01:05:17+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-24T01:05:17+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=73e66b703258270a3d688a51dd90ed2f24401568'/>
<id>urn:sha1:73e66b703258270a3d688a51dd90ed2f24401568</id>
<content type='text'>
mount_btrfs_subvolumes and generate_btrfs_fstab each carried an identical block that composed a subvolume's mount options from BTRFS_OPTS plus the per-subvol extra flags. The two could drift out of sync. Extracted the logic into parse_btrfs_subvol_opts (pure string transform), preserving the exact behavior, and called it from both. Added bats cases covering the default, compress=no, nodatacow, nosuid, and combined paths.
</content>
</entry>
<entry>
<title>fix(installer): RAID validation, set -e fix, drop dead shadow branch</title>
<updated>2026-06-24T00:55:07+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-24T00:55:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=edb5016809f3bc657283d8c2402970dbbab3c5cf'/>
<id>urn:sha1:edb5016809f3bc657283d8c2402970dbbab3c5cf</id>
<content type='text'>
Two installer cleanups from the todo backlog.

validate_config now rejects a RAID_LEVEL the selected disk count can't support, guarding the unattended path (the interactive path already constrains the choice). While adding it I found a latent bug: the error loop's ((errors++)) returned 0 on the first error and tripped set -e in the monolith's `[[ UNATTENDED == true ]] &amp;&amp; validate_config` call, aborting after one warning instead of listing every problem. Switched to pre-increment so the count accumulates as designed. Added four bats cases, including one that runs validate_config under set -e outside bats' run shield.

build.sh dropped the dead shadow-file rebuild else-branch. The profile is always copied fresh from releng (which ships /etc/shadow), so the branch never ran, and its hardcoded account list had drifted from what releng provides. Replaced with an assertion that fails the build loudly if the file is ever missing.
</content>
</entry>
<entry>
<title>fix(build): drop sanoid from the baked AUR set</title>
<updated>2026-06-17T05:13:15+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-17T05:13:15+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=448a40651e71f8be4dd6a7b3b03bb233e6894eec'/>
<id>urn:sha1:448a40651e71f8be4dd6a7b3b03bb233e6894eec</id>
<content type='text'>
sanoid depends on perl-config-inifiles, which is AUR-only. makepkg -s can't resolve it from the official repos, so the build aborts before it produces an ISO. The 2026-06-09 dependency gate checked AUR-RPC existence rather than the official sync dbs, so it wrongly classified perl-config-inifiles as official. A full build caught it.

sanoid joins paru and mkinitcpio-firmware as AUR-of-AUR packages deferred to the vNext dependency-resolution work. The v1 baked set is now eight packages. Updated the tests and README to match.
</content>
</entry>
<entry>
<title>feat(install): install baked AUR packages and clean the target config</title>
<updated>2026-06-10T04:45:00+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-10T04:45:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=4e6f4cc66206f02e92d4a2ca2f414fad5a3439a1'/>
<id>urn:sha1:4e6f4cc66206f02e92d4a2ca2f414fad5a3439a1</id>
<content type='text'>
Wire the baked AUR repo into the installer. Before pacstrap, install_base checks whether the ISO shipped the repo and, if so, exposes [aur] in the live /etc/pacman.conf and reads the package names from the manifest, adding them to the pacstrap set so they install into the target offline. This mirrors the existing [archzfs] handling. pacstrap resolves repos from the live system, not $MNTPOINT.

The live config already carries [aur] from the shipped ISO config, so the append is idempotent by design. A --skip-aur ISO ships no repo, and aur_repo_available gates the whole path, so the installer still works there.

configure_system strips any [aur] stanza from the target /etc/pacman.conf. pacstrap installs a stock target config with no [aur], so this is defensive, but it guarantees the installed system never references /usr/share/aur-packages, which exists only on the live ISO.

Four new common.sh helpers carry the logic: aur_repo_available, append_aur_repo (idempotent), aur_manifest_names (the manifest is the source of what to install, so the list never drifts), and strip_repo_stanza. All four covered across Normal, Boundary, and Error.
</content>
</entry>
<entry>
<title>feat(build): inject the AUR repo into the profile and live ISO</title>
<updated>2026-06-10T04:41:41+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-10T04:41:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=39b4a8bc5cac3a2092122f6c4fbede9bf0139286'/>
<id>urn:sha1:39b4a8bc5cac3a2092122f6c4fbede9bf0139286</id>
<content type='text'>
Wire build-aur.sh into build.sh. After the pacoloco block, build the AUR repo and append a build-host [aur] stanza to profile/pacman.conf with an absolute file:// Server, so mkarchiso installs the baked packages into airootfs. The stanza lands after the pacoloco rewrite so its file:// path isn't redirected to localhost.

Add the audited official extra packages and the baked AUR names to packages.x86_64, both sourced from build-aur.sh so the list never drifts from the build array. Ship the repo into airootfs and write a complete live /etc/pacman.conf: the pristine releng config with [aur] appended, not an [aur]-only file, since this replaces the live system's stock config and an AUR-only one would strip the official repos. Copy the manifest beside the ISO in out/.

--skip-aur skips the build, the stanza, the AUR names, and the live config. The three injection points also guard on the repo dir existing, so the documented empty-set path can't point mkarchiso at a missing repo. Moved BUILD_LOG creation ahead of the AUR build so its output is captured too.

A unit test reproduces the live-config construction and asserts core, extra, the mirrorlist, and [aur] all survive. The end-to-end proof that mkarchiso installs from the build-host repo needs a real root build and is tracked as manual verification.
</content>
</entry>
<entry>
<title>feat(build): add AUR local-repo build helpers</title>
<updated>2026-06-10T04:37:07+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-10T04:37:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=5e43d8c4ad8685e88331ac78641ca84666cb9e7a'/>
<id>urn:sha1:5e43d8c4ad8685e88331ac78641ca84666cb9e7a</id>
<content type='text'>
Add build-aur.sh, sourced by build.sh, that builds the v1 genuine-AUR set into a local pacman repo and emits an auditable manifest. The pure helpers carry the testable surface: the package sets (one source of truth for the build array and the package-list append), the [aur] stanza renderer, the TSV manifest header/row, the package-file locator, the staged repo replacement, and the build-environment preflight.

makepkg refuses to run as root, so the orchestrator drops to $SUDO_USER for the clone and build. It stages on the same filesystem and swaps in with mv -T on full success, so a failure ships no repo and leaves no stale one. On any failure error() names the package, the phase, and the log path.

The orchestrator and manifest-append need root, network, and makepkg, so they stay out of bats and are covered by the build integration test and the manual checklist instead. Eighteen unit tests cover the pure helpers across Normal, Boundary, and Error.
</content>
</entry>
<entry>
<title>test: cover disk_in_use and network_available failure paths</title>
<updated>2026-05-23T09:08:53+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-23T09:08:53+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=98fc424f7edb26314ffe124d3bc24549146a06d5'/>
<id>urn:sha1:98fc424f7edb26314ffe124d3bc24549146a06d5</id>
<content type='text'>
These two boundary functions backed the pre-flight guards from #215 but had no unit coverage of their own. The VM harness exercised them instead. I added 7 bats tests that mock the system commands they query, so the real branching logic runs.

test_disk.bats covers disk_in_use across mountpoint, active swap, imported-zpool member, and idle — that's the gate that refuses to wipe an already-mounted disk. test_archangel.bats covers network_available for DNS failure, TCP-connect failure, and success, the check that fails the install before pacstrap. The /proc/mdstat-positive branch and the live probes stay in the VM harness, since neither drives cleanly without writing to /proc or hitting the network. Suite 238 to 245, lint clean.
</content>
</entry>
<entry>
<title>fix(build): clear stale archzfs from the pacoloco cache too</title>
<updated>2026-05-23T01:28:15+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-23T01:28:15+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=bed054f46e3b41aae0d599ed7fbc3e1e42d6ddd7'/>
<id>urn:sha1:bed054f46e3b41aae0d599ed7fbc3e1e42d6ddd7</id>
<content type='text'>
archzfs re-uploads its GitHub release assets under the same filename, so pacoloco keeps serving a zfs-dkms/zfs-utils it cached earlier while pacman fetches a fresh archzfs.db with a new checksum. The two mismatch and pacstrap aborts with "invalid or corrupted package." build.sh already drops the stale packages from the host pacman cache, but it never cleared the pacoloco layer, which the VM test installs route through too, so test-install.sh kept hitting the corruption (four times in one session).

build.sh runs as root, so it now clears /var/cache/pacoloco/pkgs/archzfs/zfs-* alongside the host cache, which makes the build-then-test flow self-healing. The pacoloco cache is root-owned and test-install.sh runs as the user, so it can't clear it unattended. Instead, test-install.sh now recognizes the corruption (is_archzfs_cache_corruption) and prints how to clear it, the way it already names the SSH_PORT override on a port collision. A retry alone won't help since it hits the same cached file, so this fails fast with the hint rather than retrying.
</content>
</entry>
<entry>
<title>fix(test): fail clearly when the VM forward port is taken</title>
<updated>2026-05-23T01:12:14+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-23T01:12:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archangel/commit/?id=0f8bbc7c1e2c2f6fec0b17753ac0d9c4a3ad4317'/>
<id>urn:sha1:0f8bbc7c1e2c2f6fec0b17753ac0d9c4a3ad4317</id>
<content type='text'>
A test run launched qemu without first checking the SSH forward port, so a collision with another VM already holding it surfaced only as an opaque "Failed to start VM," with qemu unable to bind and no hint why. I added a port_in_use check in run_test before the launch: it errors with the port number and the SSH_PORT override to set, records the failure, and moves on.

The check lives in run_test, not start_vm, because start_vm runs in a command substitution (vm_pid=$(start_vm ...)) where this harness's non-exiting error() would be captured as the PID instead of failing the run. The pure half, port_listening_in, takes an `ss -tln` snapshot as a string so it's unit-testable.
</content>
</entry>
</feed>
