<feed xmlns='http://www.w3.org/2005/Atom'>
<title>archsetup/tests/installer-steps, branch main</title>
<subtitle>Builds a full dev workstation from a bare Arch Linux install.
</subtitle>
<id>https://git.cjennings.net/archsetup/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/archsetup/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/'/>
<updated>2026-08-09T16:47:36+00:00</updated>
<entry>
<title>feat: enable the podman API socket and ship the camera udev grant</title>
<updated>2026-08-09T16:47:36+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-08-09T16:20:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=169dab71a063f4fab6c46f95e11d6e85b140cb8f'/>
<id>urn:sha1:169dab71a063f4fab6c46f95e11d6e85b140cb8f</id>
<content type='text'>
Two one-time machine setups from the winvm handoffs now happen at install time.

- The devops podman block enables the rootless podman API socket. Socket-activated, so it costs nothing idle, and API clients like Pods fail with an empty window without it. enable_user_service grew an optional wants-target argument because a socket unit's [Install] is WantedBy=sockets.target. The old default.target link would never socket-activate.
- install_camera_passthrough_rules ships 72-usb-passthrough-cameras.rules: GROUP="video", MODE="0660" plus the uaccess tag on the OBSBOT and BRIO USB IDs, so usbredirect can claim them for VM passthrough. The filename is load-bearing: logind's ACL is applied by 73-seat-late.rules, so the tag only works from a file sorting below 73. A test pins that property.

Both are live on ratio (the old 99- rules file is retired there). Whether uaccess alone would suffice from the corrected position is untested and stays documented as a hypothesis.
</content>
</entry>
<entry>
<title>feat: automate microcode, TLP radio enable, and ZFS tmp.mount mask</title>
<updated>2026-08-09T16:47:36+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-08-08T09:55:52+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=fca837883b0b79793ea81d223d6e08a8ee2b2248'/>
<id>urn:sha1:fca837883b0b79793ea81d223d6e08a8ee2b2248</id>
<content type='text'>
The 2026-04 velox setup left three manual fixes behind. All three now happen at install time.

- The TLP config sets DEVICES_TO_ENABLE_ON_STARTUP="bluetooth wifi". systemd-rfkill is masked on laptops because it fights TLP, so TLP is the only thing left that can restore radio state. Without this a fresh install can boot with both radios soft-blocked.
- mask_tmp_mount_for_zfs masks systemd's tmp.mount when the pool carries a dataset mounted at /tmp, so tmpfs can't shadow the dataset and break systemd-tmpfiles.
- install_cpu_microcode installs intel-ucode or amd-ucode by vendor_id, first in boot_ux so grub-mkconfig and mkinitcpio's microcode hook both see the ucode image.

New tests cover each function across normal, boundary, and error cases. The boot_ux and snapshot-dispatch sequence pins include the new calls.
</content>
</entry>
<entry>
<title>fix(installer): harden the lock path against the AMD-iGPU DPMS lockout</title>
<updated>2026-07-25T04:16:56+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-25T04:16:56+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=14ddfe269477637c1434d7454175ed77f6907479'/>
<id>urn:sha1:14ddfe269477637c1434d7454175ed77f6907479</id>
<content type='text'>
An idle lock on this Strix Halo box wedged the whole session: hyprlock died and the compositor stayed locked with no prompt, recoverable only from a console. It's a documented AMD-integrated-Radeon failure (hyprlock#953, Hyprland#5822) -- a display power cycle via DPMS invalidates the GPU resources the lock client holds, so hyprlock loses its surface and exits without unlocking. No coredump, no OOM; the GPU pulls the rug out.

Two installer changes, both scoped and tested:

update_grub_cmdline adds amdgpu.runpm=0 on AMD machines only. Disabling GPU runtime power management keeps those resources valid across a display cycle -- the root fix. A no-op on Intel/NVIDIA, and it rides the existing merge so no boot-critical token is touched.

configure_hyprlock_pam writes a complete PAM stack. The hyprlock package ships only `auth include login`, leaving account and session uninitialised so pam_end() crashes on cleanup -- a separate documented lockout cause. All three phases now resolve through login, inheriting the keyring the graphical login uses.

CALL_SITES pins both new wirings. 372 unit tests, exit 0; each addition proven by reverting it.
</content>
</entry>
<entry>
<title>fix(installer): derive the wireless regdom by pattern and retire two inert hooks</title>
<updated>2026-07-24T17:57:29+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-24T17:57:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=6f286be20628453776235d9a9f5201a888accd78'/>
<id>urn:sha1:6f286be20628453776235d9a9f5201a888accd78</id>
<content type='text'>
The regulatory domain came from a fixed offset, "${current_lang:3:2}", which is right only for a two-letter language code. validate_config accepts three-letter languages and glibc ships 75 of them, so ber_DZ.UTF-8 yielded "_D" and C yielded "". A garbage region matches no line in the regdom file, sed exits 0, and the || error_warn could never fire, so WiFi sat on the conservative "00" domain in silence.

locale_country matches the _CC group instead of counting characters, and set_wireless_regdom verifies the substitution actually landed. That second half is the part worth keeping: every sed -i in this installer shares the stance that a no-match is indistinguishable from success, and this is the one where the silence costs something.

switch_udev_hook_to_systemd now rewrites keymap and consolefont to sd-vconsole when it performs the swap. Both are busybox-only run_hook scripts, so a systemd initramfs installs them and never runs them, and this machine's own HOOKS line carries all three today. Cosmetic on a box that just boots, and not cosmetic on one asking for a passphrase with a non-US layout, which is exactly what sd-vconsole restores. The two collapse into one entry and an existing sd-vconsole is not duplicated.

Before building it I answered the question the task left open. This machine is KEYMAP=us with no encrypt hook, so no prompt, but the function runs on LUKS machines where there is one, and sd-vconsole ships with mkinitcpio.

364 unit tests, exit 0. Adds tests/installer-steps/test_wireless_regdom.py (16) and 7 sd-vconsole cases. Every guard proven by deleting it, including one whose first version passed with the guard gone because a later check caught the same case.
</content>
</entry>
<entry>
<title>fix(installer): eight fixes from an overnight bug-hunt and its review</title>
<updated>2026-07-24T17:19:04+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-24T17:19:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=a9067341a3ca358c115531e76c51462ca948e76d'/>
<id>urn:sha1:a9067341a3ca358c115531e76c51462ca948e76d</id>
<content type='text'>
I squashed these because the per-bug reasoning lives in todo.org, which this commit carries.

Two could cost a machine. configure_initramfs_hook swapped the udev hook for systemd on a LUKS root, leaving a standalone encrypt hook under an init that never runs it. The rebuild succeeds and the installer exits clean, then the root won't unlock at the next boot. trim_firmware ran pacman -Rdd against twelve firmware packages behind a DMI gate reading product_name, where "Framework" never appears. That left it dead on the hardware it targets, and dangerous to fix the obvious way: this machine is a Framework Desktop whose Ryzen iGPU needs the amdgpu firmware. It refuses on PCI modalias evidence now.

wipedisk discarded before it checked. blkdiscard ran with -f, which disables the exclusive open, so picking the wrong disk destroyed a live filesystem and then reported that nothing had happened.

Four more are smaller. The NVIDIA preflight aborted dwm and headless installs over a driver floor they never need. zfs-replicate exited 0 after every dataset failed. Unattended installs blocked on two prompts, and the first fix for that inherited a [Y/n] default into passwordless console login. A fresh install left the dotfiles repo permanently dirty.

The review found a pattern worth more than any single fix. Helpers had thorough tests and none proved they were called. Deleting the call left five suites green, including the guard on that pacman -Rdd. CALL_SITES now pins nine caller/callee pairs.

The suite runs 341 tests at exit 0, with no new shellcheck findings. I proved every guard by deleting it and watching the intended test go red.
</content>
</entry>
<entry>
<title>fix(installer): merge GRUB cmdline instead of overwriting it</title>
<updated>2026-07-21T11:50:12+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-21T11:50:12+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=1ca59a335959c607074d68630696864297d2da74'/>
<id>urn:sha1:1ca59a335959c607074d68630696864297d2da74</id>
<content type='text'>
configure_grub replaced the whole GRUB_CMDLINE_LINUX_DEFAULT line with a fixed string. A base install that had set cryptdevice=, resume=, zfs=, or any other boot-critical parameter lost it, and the grub-mkconfig that follows baked an unbootable config.

update_grub_cmdline now reads the current value and merges: every existing token survives, archsetup's tokens are added, and where both set the same key archsetup's value wins. A safety check refuses to write when any existing token's key would vanish from the merge, and the write goes through awk + mv so paths with slashes can't break the substitution. The block also backs up /etc/default/grub before editing, which the other system-file edits already did.
</content>
</entry>
<entry>
<title>test: make the pacman hook-order test measure the source</title>
<updated>2026-07-21T04:36:40+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-21T04:36:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=d4309e354f7c624b649cd5fdb0f970c0bed4c595'/>
<id>urn:sha1:d4309e354f7c624b649cd5fdb0f970c0bed4c595</id>
<content type='text'>
The ordering asserts compared two string literals to each other ("05..." &lt; "60..."), which is constant-true and never read the file. The invariant it claims to guard is boot-critical: pacman runs hooks in filename order, and a guard hook renamed past 60-mkinitcpio-remove would let a blocked transaction remove the current initramfs with this test still green.

The test now extracts the hook filenames the installer actually writes and compares those against the stock mkinitcpio hook name, so a rename flows into the assertion. Verified by mutation: renaming 05-zfs-snapshot to 70- in a source copy fails the new extraction compare where the old literal compare stayed true. Also added: every written hook must carry a two-digit ordering prefix.
</content>
</entry>
<entry>
<title>fix(installer): rebuild initramfs after the nvme MODULES edit</title>
<updated>2026-07-21T04:30:56+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-21T04:30:56+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=dbddae6c10645c76aaf9f49b0bf5d360377c3a6b'/>
<id>urn:sha1:dbddae6c10645c76aaf9f49b0bf5d360377c3a6b</id>
<content type='text'>
The nvme early-load edit landed in mkinitcpio.conf, but the only nearby mkinitcpio -P ran behind an is-not-zfs-root gate. On ZFS-root machines (all of mine) the hardening was never compiled into the initramfs, which is exactly the case the early load exists for. The extracted ensure_nvme_early_module now rebuilds whenever it changed the file, and skips the rebuild on an unchanged conf so a resume stays idempotent.

The already-present check also grepped the whole file for "nvme", so a comment or nvme_tcp anywhere skipped the edit. It now checks for the nvme word on the MODULES line only.
</content>
</entry>
<entry>
<title>refactor(installer): extract validate_yesno and a NVIDIA driver-floor constant</title>
<updated>2026-07-20T21:00:39+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-20T21:00:39+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=04d344d90449d3fe4f0d0b474b0b50e61ff2a79a'/>
<id>urn:sha1:04d344d90449d3fe4f0d0b474b0b50e61ff2a79a</id>
<content type='text'>
Four near-identical blocks validated that AUTOLOGIN, NO_GPU_DRIVERS, INSTALL_CLAUDE_CODE, and INSTALL_DEVICE_UDEV_RULES were empty or exactly yes/no. They collapse into a validate_yesno helper the callers guard with `|| exit 1`.

The NVIDIA driver floor of 535 was a magic literal in the comparison and three prose lines. It now lives in a NVIDIA_MIN_DRIVER constant, and the nvidia-preflight test sources that constant alongside the function it extracts.

This is the safe, purely-testable slice of the refactor rollup. The structural extractions that touch the installer's system-mutation paths -- the GPU modalias scan, the stow and retry loops, the display-server/window-manager dispatch, and the snapper/fsck/GRUB/waybar sed blocks -- are split into a follow-up so they get characterization coverage first rather than a blind rewrite.
</content>
</entry>
<entry>
<title>fix(installer): guard the cp and chmod that install helper scripts</title>
<updated>2026-07-20T20:52:40+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-20T20:52:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=eec95de4d681464e691e17269e6593e119c32b29'/>
<id>urn:sha1:eec95de4d681464e691e17269e6593e119c32b29</id>
<content type='text'>
With `set -e` off, the installer copied its zfs-replicate, zfs-pre-snapshot, and hypr-live-update-guard scripts into place with bare cp/chmod. A missing or partial source failed silently, leaving a systemd service with a dead ExecStart or a pacman hook pointing at a script that never landed.

The two zfs scripts now install through install_executable, which guards the copy, warns on failure, and skips the chmod when the copy did not land. The two live-update-guard chmods (the guard binary and its hook) are guarded inline with error_warn.
</content>
</entry>
</feed>
