<feed xmlns='http://www.w3.org/2005/Atom'>
<title>archsetup/tests/installer-steps/test_orchestrators.py, branch claude/hungry-franklin-09fffc</title>
<subtitle>Builds a full dev workstation from a bare Arch Linux install.
</subtitle>
<id>https://git.cjennings.net/archsetup/atom?h=claude%2Fhungry-franklin-09fffc</id>
<link rel='self' href='https://git.cjennings.net/archsetup/atom?h=claude%2Fhungry-franklin-09fffc'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/'/>
<updated>2026-08-14T09:27:11+00:00</updated>
<entry>
<title>feat(installer): set a 90-minute suspend-to-hibernate ceiling</title>
<updated>2026-08-14T09:27:11+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-08-14T09:27:11+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=5f52d3887c610b93c6918d637f63cc326237aea5'/>
<id>urn:sha1:5f52d3887c610b93c6918d637f63cc326237aea5</id>
<content type='text'>
systemctl suspend-then-hibernate reads HibernateDelaySec, and unset it depends on the hardware: a machine with a battery hibernates on a low-battery alarm at an hour nobody chose, and one without falls back to systemd's 2h default. 90 minutes states a ceiling both machines share. A laptop can still hibernate earlier, because systemd runs both timers and takes whichever fires first.

The number is a tradeoff and the drop-in carries it: long enough that stepping out of a meeting costs a screen unlock rather than a passphrase and a full resume, short enough that a laptop left in a bag drops its encryption keys out of RAM. A suspended machine is still holding them.

Numbered 60- because systemd reserves 10-40 for vendor drop-ins under /usr and 60-90 for local ones under /etc. A 10- file here would sort below a vendor drop-in and quietly lose to it.
</content>
</entry>
<entry>
<title>feat(installer): grant brightness control to the video group</title>
<updated>2026-08-14T03:34:42+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-08-14T03:34:42+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=a42e06d6ef4fc6296a17c69580aa9911667a6c88'/>
<id>urn:sha1:a42e06d6ef4fc6296a17c69580aa9911667a6c88</id>
<content type='text'>
Arch's brightnessctl ships no udev rules and leans on logind, which grants brightness writes only to the active seat session. Anything outside that session (a script, an ssh shell, a panel launched into a different one) meets root-owned sysfs and gets EPERM. On velox both panel sliders were dead after a fresh install.

configure_backlight_access writes a udev rule making the backlight and keyboard-LED brightness attributes group-writable by video, which create_user already adds the user to. I granted the keyboard LED to video rather than input: anyone in input can read every input device, which is too much authority for dimming a keyboard.

essential_services is already marked complete on machines installed before this step, so a re-run skips them. The post-install checklist carries the manual route.
</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): 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>feat(install): install maintenance thresholds and enable maint timers</title>
<updated>2026-07-08T11:17:31+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-07-08T11:17:31+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=6fe2913dc01330edaa4e8b36d06a250e63c20b6b'/>
<id>urn:sha1:6fe2913dc01330edaa4e8b36d06a250e63c20b6b</id>
<content type='text'>
The maint console and the system-health-check workflow grade against ~/.config/archsetup/maintenance-thresholds.toml, but nothing installed it. A fresh install got a working maint CLI and no thresholds file.

user_customizations now installs the shipped TOML from the cloned repo. A re-run refreshes it, and the user layer in ~/.config/maint/ is never touched, so a reinstall can't eat curation. It also enables maint-scan.timer and maint-net-scan.timer through wants-symlinks (systemctl --user has no session bus during install, the syncthing idiom). Timer enablement is hyprland-only because the units live in that stow tier.

The install lists gain the runtime tools the probes call but nothing carried: expac, lm_sensors, fwupd.
</content>
</entry>
<entry>
<title>feat: install pre-pacman ZFS snapshot hook on ZFS-root systems</title>
<updated>2026-06-30T11:56:41+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-30T11:56:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=db045b2f8296f450bf29053ad66e708f00b48ca0'/>
<id>urn:sha1:db045b2f8296f450bf29053ad66e708f00b48ca0</id>
<content type='text'>
archsetup took sanoid from install-archzfs but never ported the pre-pacman snapshot hook, so a ZFS-root install had no transaction-triggered rollback point — the working setup only existed as a hand-placed script on velox, lost on reinstall. Add configure_pre_pacman_snapshots(): a PreTransaction pacman hook plus a self-pruning script that keeps the 10 most recent pre-pacman snapshots (sanoid ignores them — they aren't autosnap_ names). It's gated to ZFS-root and runs late in boot_ux, so the hook doesn't fire during the install's own package operations and the first snapshot is the fresh system.

The script ships as scripts/zfs-pre-snapshot, made ZFS_PRE_* env-overridable so the pruning logic is unit-testable. Unit tests drive it against a fake zfs (creates a snapshot, prunes the oldest past KEEP, ignores non-pre-pacman snapshots, honors the lockfile interval, warns on failure); a Testinfra test asserts the hook and script land on a ZFS install; the orchestrator test pins the new boot_ux substep.
</content>
</entry>
<entry>
<title>test: pin installer orchestrator call sequences</title>
<updated>2026-06-27T17:59:13+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-27T17:59:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/archsetup/commit/?id=fd2601393fa8eca01a4354a7ec1d0bc793f7c9c6'/>
<id>urn:sha1:fd2601393fa8eca01a4354a7ec1d0bc793f7c9c6</id>
<content type='text'>
The decomposition left each big step function as a thin list of sub-step
calls with no runtime coverage. These tests sed-extract each orchestrator,
stub its sub-functions as recorders, and assert the exact call order, so a
dropped or reordered step fails the suite. configure_snapshots also gets a
per-filesystem dispatch check (zfs / btrfs / other).
</content>
</entry>
</feed>
