Jesse A. Green / notebook

Static replay of the provisioning script's own decision logic (v2.5, Oct 2026). Every combination below was evaluated by the real script on the author's PC and recorded; this page only looks it up. No machine is touched.

Strix Halo laptop, 128 GB

ASUS ROG Flow tablet laptop (GZ302 board, 2025): AMD Ryzen AI Max+ 395 with Radeon 8060S graphics and 128 GB of unified memory.

The machine the script sees

Vendor / board
ASUSTeK COMPUTER INC. / GZ302EA
Chassis type
32
CPU
AMD RYZEN AI MAX+ 395 w/ Radeon 8060S
Graphics (lspci -nn)
Display controller [0380]: Advanced Micro Devices, Inc. [AMD/ATI] Strix Halo [Radeon 8060S] [1002:1586] (rev c1)
RAM
124 GiB (MemTotal)
Detected
the script's GZ302 class; laptop; ASUS laptop asusd can drive; Strix Halo APU

What it decided

124 GiB

124 GiB is what the 128 GB node reports with the minimum BIOS carve-out.

Ollama backend OLLAMA_BACKEND
ROCm INSTALL_ROCM
PyTorch INSTALL_PYTORCH
Hibernate HIBERNATE
Disk maintenance (opt-in) ENABLE_SMARTD + ENABLE_SCRUB

Every other CONFIG value stays at the script's default (list below).

  • OLLAMA_BACKEND=auto (Ollama backend)
  • INSTALL_ROCM=auto (ROCm)
  • INSTALL_PYTORCH=auto (PyTorch)
  • HIBERNATE=1 (Hibernate)
  • ENABLE_SMARTD=0 (Disk maintenance (opt-in))
  • RAM: 124 GiB
Ollama backend
vulkan
ROCm has an open >4k-token bug on gfx1151
PyTorch wheels
rocm-gfx1151
auto: only where a supported GPU exists
ROCm
yes
auto: Strix Halo only
CUDA toolkit
no
auto: Turing+ only (Arch cuda 13 dropped Pascal)
GPU may pin
118 GiB
ttm.pages_limit=30932992; auto = min(120, RAM - 6) GiB the GPU may pin. 1 GiB = 262144 pages
zram size
15.5 GiB
auto: min(ram / 8, 16384) MiB on Strix Halo (the iGPU's memory comes out of RAM)
thermald
no
auto: Intel laptops except Razer (EC-managed; thermald would reset the powercap step's PL1 to the firmware's 60 W)
Power profile owner
asusd (power-profiles-daemon masked)
ASUS: asusd owns it; others: power-profiles-daemon (perfprofile step)

The plan: 28 ordered steps

23 apply on this machine

0-preflight

  1. 01preflightPreflight checks + hardware detectionapplies

    sanity checks + hardware detection + hostname

    Stops the run if

    • run as your normal user, not root (AUR builds refuse root); admin rights are used where needed
    • admin tool missing
    • admin authentication failed
    • /etc/os-release missing
    • unsupported distro ID='<ID>' (expected cachyos)
    • no network (curl to archlinux.org failed)

    Settings it reads

    • HIBERNATE=1

    Troubleshooting hint

    Run as your normal user (not root) with admin rights, on CachyOS, with network. Hibernation needs disk swap (zram does not count): a swap partition or an active swap file is used as is; with neither and a btrfs / (the Calamares automatic layout) the hibernate step creates a swap file in subvolume @swap at /swap (SWAPFILE_GIB, auto from RAM); with neither on ext4/xfs add a swap partition or set HIBERNATE=0. 'bash node-setup.sh --report' shows what was detected.

1-base

  1. 02updateFull system update + base tools + paruapplies

    full system update + base tools + paru + Qt/KDE skew guard (stops for a reboot if the kernel was replaced)

    Installs

    • repo archlinux-keyring cachyos-keyring
    • repo base-devel git curl wget rsync openssh jq bc usbutils pciutils lm_sensors htop btop tmux vim nano man-db less which inetutils dmidecode

    Needs the owner afterwards

    • Qt/KDE libraries do not resolve after the update (Qt skew between the CachyOS repos and Arch extra: the login screen / desktop will not start, SSH is fine). Run 'pacman -Syu' again later (CachyOS rebuilds), or 'pacman -S extra/<pkg>' for the package named in the warning. Check: ldd -r /usr/bin/kwin_wayland | grep undefined after a check on the node
    • the update replaced the running kernel: reboot, then continue with: bash <SCRIPT_PATH> --from lts<note> after a check on the node

    Verify checks (after the reboot)

    • OK paru present

    Settings it reads

    • QT_SKEW_FIX=1

    Troubleshooting hint

    pacman lock: 'rm /var/lib/pacman/db.lck' if no pacman/paru runs. Keyring: 'pacman -Sy archlinux-keyring cachyos-keyring'. Mirrors: 'cachyos-rate-mirrors'. "installing nvidia-utils (X) breaks dependency 'nvidia-utils=Y' required by linux-cachyos(-lts)-nvidia-open" = the CachyOS repo is caught mid-sync (the NVIDIA modules ship with each kernel build): wait an hour and re-run; never force it with -Sdd. A replaced kernel needs a reboot, then '--from lts'. Black screen / no login screen after an update = Qt skew: on the znver4 repos qt6-base came from Arch extra in Oct 2026 (the v3 repos carry CachyOS's own qt6-base), while the CachyOS repos (preferred) still held Qt modules and KDE built for the previous Qt. This step installs Qt modules behind qt6-base from extra/; other Qt/KDE packages that CachyOS has older than Arch only when the Plasma binaries fail to load. Such a package stays on Arch's build until CachyOS ships a higher version (QT_SKEW_FIX=0 = report only). Check: 'ldd -r /usr/bin/kwin_wayland | grep undefined' (also plasmalogin, plasmashell). Fix: 'pacman -Syu' again later, or 'pacman -S extra/<pkg>' for the package that owns the library named in the error ('pacman -Qo <library>').

  2. 03ltsLTS kernel as fallbackapplies

    keep linux-cachyos-lts (+ headers) as the fallback kernel

    Installs

    • repo linux-cachyos-lts linux-cachyos-lts-headers linux-cachyos-headers
    • repo linux linux-headers if IS_ASUS = 1

    Verify checks (after the reboot)

    • OK linux-cachyos-lts installed
    • OK vanilla Arch 'linux' installed (fallback for the ASUS sysfs hang) if IS_ASUS = 1, after a check on the node

    Settings it reads

    • INSTALL_LTS_KERNEL=1

    Troubleshooting hint

    linux-cachyos-lts + headers from the CachyOS repo (plus vanilla Arch 'linux' on ASUS laptops); Limine picks up new kernels via the limine-mkinitcpio hook.

2-remote

  1. 04sshSSH daemon, hardenedapplies

    hardened sshd drop-in; login without a key auto-disabled once an authorized key exists

    Installs

    • repo openssh

    Writes

    • /etc/ssh/sshd_config.d/10-node.conf 644 written by node-setup.sh - edit and 'systemctl reload sshd'

    Services

    • sshd.service enable --now

    Verify checks (after the reboot)

    • OK sshd active
    • OK listening on 22

    Settings it reads

    • SSH_PORT=22
    • SSH_AUTHORIZED_KEYS_URL=
    • SSH_AUTHORIZED_KEYS_FILE=$SCRIPT_DIR/authorized_keys

    Troubleshooting hint

    'sshd -t' shows the bad line. Login without a key is only disabled when ~/.ssh/authorized_keys has a key, so you cannot lock yourself out. Windows has no ssh-copy-id; from PowerShell: Get-Content <your public key file> | ssh <user>@<host> "mkdir -p ~/.ssh && tr -d '\r' >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"

  2. 05firewallFirewall (ufw): allow SSH + tailnetapplies

    ufw is ON by default on CachyOS: allow ssh and the tailnet interface

    Runs

    • ufw allow 22/tcp after a check on the node
    • ufw allow in on tailscale0 if INSTALL_TAILSCALE = 1, after a check on the node
    • firewall-cmd --permanent --add-port=22/tcp after a check on the node
    • firewall-cmd --reload after a check on the node

    Verify checks (after the reboot)

    • OK ufw allows 22/tcp

    Settings it reads

    • SSH_PORT=22
    • INSTALL_TAILSCALE=1

    Troubleshooting hint

    CachyOS enables ufw (deny incoming). 'ufw status verbose'. Manual: 'ufw allow ssh'.

  3. 06tailscaleTailscale daemonapplies

    tailscaled installed + enabled (the login itself is the one thing the owner must do)

    Installs

    • repo tailscale

    Writes

    • /etc/NetworkManager/conf.d/99-tailscale.conf 644 written by node-setup.sh (Arch wiki Tailscale): NetworkManager must not manage the tailnet interface after a check on the node

    Services

    • tailscaled.service enable --now

    Needs the owner afterwards

    • set key expiry for <hostname> per node policy: its node key expires <exp> and the node then drops off the tailnet (https://tailscale.com/kb/1028/key-expiry) after a check on the node
    • log <hostname> into the tailnet: tailscale up --operator=<NODE_USER> (prints a login URL; tailnet name becomes <hostname>.<tailnet name>) after a check on the node
    • after the tailnet login: set key expiry for <hostname> per node policy; a new node key expires after 180 days and the node then drops off the tailnet (https://tailscale.com/kb/1028/key-expiry) after a check on the node

    Verify checks (after the reboot)

    • OK tailscaled active if INSTALL_TAILSCALE = 1, after a check on the node

    Settings it reads

    • INSTALL_TAILSCALE=1

    Troubleshooting hint

    Package 'tailscale' is in Arch extra. NetworkManager must leave tailscale0 alone: any conf.d file with 'unmanaged-devices=interface-name:tailscale0' counts (read first; only when none has it is 99-tailscale.conf written and NetworkManager reloaded). Login is interactive: 'tailscale up --operator=<your user>' prints a URL (OpenSSH stays the SSH server; no Tailscale SSH); rename with 'tailscale set --hostname NAME'. Set key expiry per node policy (https://tailscale.com/kb/1028/key-expiry): a node whose key has expired drops off the tailnet until it is re-authenticated.

3-apps

  1. 07chromeGoogle Chrome (AUR)applies

    Google Chrome from the AUR

    Installs

    • AUR google-chrome

    Verify checks (after the reboot)

    • OK google-chrome-stable <google-chrome-stable output> if INSTALL_CHROME = 1, after a check on the node

    Settings it reads

    • INSTALL_CHROME=1

    Troubleshooting hint

    AUR builds need a normal user, base-devel and space in ~/.cache/paru. Fallback: 'pacman -S chromium'.

4-vendor

  1. 08asusctlASUS: asusctl + asusd (sole profile owner) [ASUS]applies

    asusctl + rog-control-center, asusd running, power-profiles-daemon masked

    Installs

    • repo asusctl rog-control-center

    Services

    • asusd.service enable --now after a check on the node
    • power-profiles-daemon.service disable --now after a check on the node
    • power-profiles-daemon.service mask after a check on the node

    Runs

    • asusctl info

    Verify checks (after the reboot)

    • OK asusd active
    • OK asusctl talks to asusd

    Troubleshooting hint

    Expect Repository: extra for asusctl. asusd is udev-triggered; 'journalctl -b -u asusd'. CachyOS kernels 7.0.3+ (and linux-cachyos-lts 6.18.34) can hang ASUS sysfs writes for 60 s with EIO (forum 30468): calls here are time-limited. If they keep failing, boot the vanilla Arch 'linux' entry (installed by the lts step on ASUS laptops). A hung writer cannot be killed: reboot at the machine.

  2. 09fansASUS: fan curves to max [ASUS]applies

    aggressive 8-point fan curves (set before PPT: some ROG firmware needs a curve first)

    Runs

    • asusctl fan-curve --mod-profile Performance --enable-fan-curves true after a check on the node
    • asusctl fan-curve -m Performance -e true after a check on the node

    Verify checks (after the reboot)

    • OK fan curves enabled for Performance

    Settings it reads

    • FAN_CURVE=30c:40%,40c:50%,50c:65%,60c:80%,70c:95%,80c:100%,90c:100%,100c:100%
    • FAN_PROFILE=Performance

    Troubleshooting hint

    Custom curves need the asus_custom_fan_curve hwmon device: 'grep -l asus_custom_fan_curve /sys/class/hwmon/hwmon*/name'. Without it the profile keeps the BIOS curve; a community CLI from the AUR is the alternative.

  3. 10pptASUS: power limits to board maximum [ASUS]applies

    raise power limits (ppt_pl1/pl2/pl3) to the driver's maximum for this board

    Verify checks (after the reboot)

    • OK ppt_pl1_spl at board max <cat output> W if PPT_MAX = 1, after a check on the node

    Settings it reads

    • PPT_MAX=1

    Troubleshooting hint

    Needs the asus-armoury driver and a DMI table entry for the board: 'ls /sys/class/firmware-attributes/asus-armoury/attributes/'. Values apply only while 'asusctl profile tuning true'.

  4. 11razerRazer: battery report, fans + power (opt-in) [Razer]not a Razern/a

    battery health + swelling warning; fan + power mode via razer-control-revived only with RAZER_CONTROL=1 (default: the EC's automatic fans)

    Runs

    • loginctl enable-linger <NODE_USER>

    Needs the owner afterwards

    • Razer 2018/2019 Blades have no battery charge limit: plugged in 24/7 the pack sits near full. Check monthly for swelling (case lifting, trackpad pressed up) and replace the battery if it swells.
    • Razer: the EC did not take a fixed fan speed (0 rpm reads back as automatic). Keep the EC's automatic fans (RAZER_CONTROL=0) unless a long job shows heat limits (CPU at 100 C, GPU thermal slowdown) if RAZER_FAN_RPM != 0, after a check on the node

    Verify checks (after the reboot)

    • OK fans on the EC's automatic control (RAZER_CONTROL=0) if RAZER_CONTROL != 1
    • OK Razer fan on AC: automatic if RAZER_FAN_RPM = 0, after a check on the node
    • OK Razer fan on AC: 0 rpm (the EC setpoint; there is no tachometer) if RAZER_FAN_RPM != 0, after a check on the node

    Settings it reads

    • RAZER_CONTROL=0
    • RAZER_POWER_MODE=1
    • RAZER_FAN_RPM=0

    Troubleshooting hint

    Default RAZER_CONTROL=0: the EC runs the fans automatically and keeps its power mode; the step prints the battery health (charge_full / charge_full_design), status and charge. RAZER_CONTROL=1: razer-control-revived (AUR razer-control-revived-bin, community) runs as a systemd USER service 'razercontrol' (linger keeps it running headless); the step waits up to 15 s for its socket, and since razer-cli exits 0 even on errors, 'Unknown error!' in its output counts as a failure. Its udev rule (/etc/udev/rules.d/99-hidraw-permissions.rules) sets the device permissions the tool needs on the Razer hidraw nodes; 'ls -l /dev/hidraw*' shows them. Check: 'systemctl --user status razercontrol', 'razer-cli read fan ac' (an EC setpoint: there is no tachometer).

5-power

  1. 12perfprofilePerformance power profileapplies

    performance power profile (ASUS: asusd owns it; others: power-profiles-daemon; Intel laptops except Razer: thermald)

    Installs

    • repo power-profiles-daemon if IS_ASUS != 1
    • repo thermald if IS_ASUS != 1 and THERMALD_EFF = 1

    Writes

    • /etc/systemd/system/node-performance.service 644 Node: re-assert the Performance platform profile after boot (asusd owns platform_profile) if IS_ASUS = 1

    Services

    • node-performance.service enable --now if IS_ASUS = 1
    • power-profiles-daemon.service unmask if IS_ASUS != 1, after a check on the node
    • power-profiles-daemon.service enable --now if IS_ASUS != 1
    • thermald.service enable --now if IS_ASUS != 1 and THERMALD_EFF = 1

    Runs

    • asusctl profile list if IS_ASUS = 1 and DRY_RUN != 1
    • powerprofilesctl set performance if IS_ASUS != 1
    • powerprofilesctl configure-battery-aware --disable if IS_ASUS != 1

    Verify checks (after the reboot)

    • OK ACPI platform_profile = performance if IS_ASUS = 1, after a check on the node
    • OK power-profiles-daemon masked (asusd is the only profile owner) if IS_ASUS = 1, after a check on the node
    • OK power-profiles-daemon: performance if IS_ASUS != 1, after a check on the node

    Troubleshooting hint

    ASUS: asusd is the only owner of /sys/firmware/acpi/platform_profile (PPD masked; running both races). Others: 'powerprofilesctl get' should say performance; do not also force the cpufreq governor there (intel_pstate then rejects PPD's EPP writes). thermald is skipped on Razer laptops (the EC manages them, and thermald resets PL1 to the firmware's 60 W); INSTALL_THERMALD=1 forces it there, and then the step runs 60 s of load and warns if PL1, max_perf_pct, no_turbo or the clock drop.

  2. 13powercapRazer: CPU package power cap (PL1/PL2) [Razer]not a Razern/a

    CPU package power cap PL1 45 W / 28 s, PL2 65 W, re-applied at boot and on AC plug/unplug; it only ever lowers a limit (n/a where the CPU's TDP is below the cap, e.g. the U-series Stealth 13)

    Writes

    • /usr/local/sbin/node-powercap 755 powercap_script ZONES : /usr/local/sbin/node-powercap (sh). It waits up to 30 s for the zones that
    • /etc/systemd/system/node-powercap.service 644 written by node-setup.sh (powercap step) - re-run on every AC plug/unplug by $POWERCAP_RULE
    • /etc/udev/rules.d/90-node-powercap.rules 644 written by node-setup.sh (powercap step) - the EC may reset the CPU power limits when the charger is plugged in or

    Services

    • node-powercap.service enable after a check on the node

    Verify checks (after the reboot)

    • OK node-powercap.service enabled (re-applies the cap at boot)
    • OK /usr/local/sbin/node-powercap is executable
    • OK /etc/udev/rules.d/90-node-powercap.rules re-applies the cap on AC plug/unplug

    Settings it reads

    • RAZER_PL1_W=45
    • RAZER_PL1_WINDOW_US=28000000
    • RAZER_PL2_W=65

    Troubleshooting hint

    Razer only, and only where RAZER_PL1_W is at most the CPU's TDP (constraint_0_max_power_uw: 45 W on the H-series Blades, 15 W on the U-series Stealth 13, where the step is n/a). /usr/local/sbin/node-powercap, run by node-powercap.service at boot and by /etc/udev/rules.d/90-node-powercap.rules on every AC plug/unplug (the EC may reset the limits), lowers PL1 (constraint_0_power_limit_uw + constraint_0_time_window_us) and PL2 (constraint_1_power_limit_uw) in /sys/class/powercap/intel-rapl:0 and intel-rapl-mmio:0; a limit already at or below the cap is kept (it never raises one, so a higher RAZER_PL1_W/RAZER_PL2_W than the one in force applies after a reboot). Check: 'journalctl -u node-powercap -b', 'grep . /sys/class/powercap/intel-rapl*:0/constraint_[01]_*'. thermald resets PL1: keep INSTALL_THERMALD=0. Off: RAZER_POWERCAP=0 plus 'systemctl disable --now node-powercap.service && rm /etc/udev/rules.d/90-node-powercap.rules', then reboot (the firmware's 60/80 W return). If the EC keeps resetting them: intel-undervolt with 'power package 65 45/28'.

  3. 14cpuASUS: CPU governor/EPP/boost performance [ASUS]applies

    CPU governor/EPP/boost = performance, re-applied at boot (PPD is masked there)

    Writes

    • /etc/systemd/system/node-cpu-performance.service 644 Node: CPU governor and EPP = performance

    Services

    • node-cpu-performance.service enable --now

    Verify checks (after the reboot)

    • OK governor performance
    • OK CPU boost on

    Troubleshooting hint

    ASUS only (PPD masked there). The node-cpu-performance.service rewrites governor, EPP and boost at boot.

  4. 15zramzram size cap (Strix Halo only) [Strix Halo]applies

    cap zram (CachyOS default is zram-size = RAM); swappiness stays CachyOS's (150)

    Writes

    • /etc/systemd/zram-generator.conf 644 written by node-setup.sh - CachyOS default (cachyos-settings) is zram-size = ram. if ZRAM_SIZE_EFF is set

    Needs the owner afterwards

    • remove /etc/sysctl.d/99-node.conf (vm.swappiness = <sysctl_node_swappiness output>, written by node-setup v2.4 or older): cachyos-settings sets 150 each time zram0 starts, so the file never applies after a boot. Command: rm /etc/sysctl.d/99-node.conf after a check on the node

    Verify checks (after the reboot)

    • OK zram swap active: <zramctl output>
    • OK /etc/systemd/zram-generator.conf: zram-size = <ZRAM_SIZE_EFF> if ZRAM_SIZE_EFF is set, after a check on the node
    • OK vm.swappiness <sw> (cachyos-settings 30-zram.rules, set when zram0 starts)

    Troubleshooting hint

    Strix Halo (or an explicit ZRAM_SIZE): /etc/systemd/zram-generator.conf (overrides the cachyos-settings default zram-size = ram); other machines keep CachyOS's default. vm.swappiness is set to 150 by cachyos-settings' /usr/lib/udev/rules.d/30-zram.rules each time zram0 starts (values above 100 suit in-memory swap: https://docs.kernel.org/admin-guide/sysctl/vm.html); an /etc/sysctl.d/99-node.conf from node-setup <= v2.4 never wins after a boot and can be removed. 'zramctl' shows the device. Hibernation never goes to zram.

  5. 16quirksQuirks: Wi-Fi power save, iwlwifi, MT7925applies

    Wi-Fi power save off, iwlwifi shutdown hook, MediaTek MT7925 ASPM fix where those cards exist; notes

    Writes

    • /etc/NetworkManager/conf.d/90-node-wifi-powersave.conf 644 Oct 2026: always-on node, no Wi-Fi power saving (2 = disable) after a check on the node
    • /usr/lib/systemd/system-shutdown/node-wifi-quiesce.shutdown 755 written by node-setup.sh - iwlwifi has no PCI .shutdown hook: unbind Intel Wi-Fi before reboot/poweroff after a check on the node
    • /etc/modprobe.d/mt7925e.conf 644 written by node-setup.sh - MediaTek MT7925 (Wi-Fi 7) stability: disable PCIe ASPM (community fix) if WIFI_ASPM_FIX = 1, after a check on the node

    Runs

    • nmcli general reload conf after a check on the node
    • iw dev <w> set power_save off after a check on the node
    • nmcli connection modify <c> connection.autoconnect-retries 0 after a check on the node

    Needs the owner afterwards

    • Wi-Fi is this node's only network path and the card is an Intel AX200 (8086:2723), which can fail to start after a warm reboot (iwlwifi probe error -110) until it loses power. Recovery at the machine: charger out, hold power about 15 s until it is off, wait 30 s, charger in, power on. Attended reboots until 3 clean reboots; a USB Ethernet adapter on the router removes the risk after a check on the node

    Verify checks (after the reboot)

    • OK <w>: Wi-Fi power save off
    • OK iwlwifi shutdown hook installed (/usr/lib/systemd/system-shutdown/node-wifi-quiesce.shutdown)

    Settings it reads

    • WIFI_ASPM_FIX=1

    Troubleshooting hint

    Wi-Fi: /etc/NetworkManager/conf.d/90-node-wifi-powersave.conf (wifi.powersave = 2, read at the next connection) plus 'iw dev <if> set power_save off' for the running one ('iw dev <if> get power_save' = off); the active Wi-Fi connection retries forever (autoconnect-retries 0). Intel Wi-Fi bound to iwlwifi: /usr/lib/systemd/system-shutdown/node-wifi-quiesce.shutdown unbinds it before reboot/poweroff (iwlwifi has no PCI shutdown hook). An Intel AX200 stuck at 'probe ... failed with error -110' only recovers from a cold boot: charger out, hold power ~15 s, wait 30 s, charger in, power on. MT7925 (PCI 14c3:7925): /etc/modprobe.d/mt7925e.conf; reload: 'modprobe -r mt7925e && modprobe mt7925e'.

  6. 17kparamsEXTRA_KERNEL_PARAMS on the kernel cmdlineEXTRA_KERNEL_PARAMS is emptyn/a

    EXTRA_KERNEL_PARAMS on the kernel command line (any machine; n/a while it is empty)

    Kernel parameters

    • EXTRA_KERNEL_PARAMS (keys another step owns are left to that step)

    Verify checks (after the reboot)

    • OK <p> active in /proc/cmdline

    Settings it reads

    • EXTRA_KERNEL_PARAMS=

    Troubleshooting hint

    EXTRA_KERNEL_PARAMS go to the same managed place as the script's other kernel parameters (Limine: the one KERNEL_CMDLINE[default]+= line in /etc/limine-entry-tool.d/50-node-setup.conf, then 'limine-update'); a key already there gets the new value. Keys another step owns on this machine are not applied here (that step would remove them again on every run): amd_iommu, ttm.pages_limit and amdgpu.gttsize on Strix Halo (IOMMU_OFF=1, TTM_PAGES_LIMIT_GIB, GTT_LEGACY=1), resume and resume_offset while HIBERNATE=1 (the hibernate step computes them). To remove one: delete it from that line, then 'limine-update'. After the reboot: cat /proc/cmdline.

6-gpu

  1. 18gpumemStrix Halo: iGPU unified-memory kernel param [Strix Halo]applies

    kernel parameter so the iGPU can pin most of RAM (ttm.pages_limit)

    Installs

    • repo, if available amd-debug-tools

    Kernel parameters

    • ttm.pages_limit=<TTM pages>

    Verify checks (after the reboot)

    • OK ttm.pages_limit = <TTM_GIB_EFF> GiB active

    Settings it reads

    • GTT_LEGACY=0
    • IOMMU_OFF=0

    Troubleshooting hint

    Limine: the script's parameters live in /etc/limine-entry-tool.d/50-node-setup.conf (one KERNEL_CMDLINE[default]+=... line, no quotes, no comment after it; /etc/default/limine keeps the installer line) then 'limine-update'; check: grep cmdline /boot/limine.conf (no quote or # in it). systemd-boot: /etc/sdboot-manage.conf then 'sdboot-manage gen'. GRUB: /etc/default/grub then grub-mkconfig. Verify after reboot: cat /sys/module/ttm/parameters/pages_limit (pages; /262144 = GiB).

  2. 19nvidiaNVIDIA: driver branch, hibernate-safe boot, CUDA [NVIDIA]no NVIDIA GPUn/a

    right driver branch (Pascal 580xx / Turing+ open), no NVIDIA in early boot (hibernation), CUDA

    Installs

    • repo linux-cachyos-headers if NVIDIA_GEN = legacy
    • repo linux-cachyos-lts-headers if NVIDIA_GEN = legacy, after a check on the node
    • repo, if available linux-cachyos-lts-nvidia-open if NVIDIA_GEN != legacy, after a check on the node
    • repo, if available nvidia-open if NVIDIA_GEN != legacy and IS_ASUS = 1 and (NVIDIA_GEN = turing or NVIDIA_GEN = ampere+), after a check on the node
    • repo cuda cudnn if CUDA_EFF = 1

    Driver profile

    • (chwd picks automatically) if NVIDIA_GEN != legacy, after a check on the node

    Writes

    • /etc/modprobe.d/99-node-nvidia-580xx-sleep.conf 644 written by node-setup.sh - legacy 580xx driver: keep video memory across hibernate (no kernel suspend notifiers in 580) if NVIDIA_GEN = legacy
    • /etc/mkinitcpio.conf.d/99-node-no-nvidia-early.conf 644 written by node-setup.sh - keep NVIDIA out of the initramfs: early loading breaks resume from hibernation
    • /etc/modprobe.d/50-node-nvidia-rtd3.conf 644 written by node-setup.sh - Turing laptop: let the dGPU power down when idle (NVIDIA_PERSISTENCE=0) if NVIDIA_PERSISTENCE != 1 and NVIDIA_GEN = turing and IS_LAPTOP = 1
    • /etc/environment.d/90-node-cuda.conf 644 written by node-setup.sh - performance AI node: undo CachyOS nvidia-utils' CUDA_DISABLE_PERF_BOOST=1

    Services

    • nvidia-suspend.service enable if NVIDIA_GEN = legacy
    • nvidia-hibernate.service enable if NVIDIA_GEN = legacy
    • nvidia-resume.service enable if NVIDIA_GEN = legacy
    • nvidia-powerd.service disable if (NVIDIA_GEN = turing or NVIDIA_GEN = legacy), after a check on the node
    • nvidia-persistenced.service enable --now if NVIDIA_PERSISTENCE = 1, after a check on the node

    Verify checks (after the reboot)

    • OK nvidia-smi: <nvidia-smi output>
    • OK NVIDIA kept out of the initramfs images that boot (hibernation-safe): <NVIDIA_IMG>
    • OK 580xx: PreserveVideoMemoryAllocations=1

    Settings it reads

    • NVIDIA_PERSISTENCE=1

    Troubleshooting hint

    Pascal/Maxwell/Volta (GP/GM/GV) need nvidia-580xx-dkms; Turing+ (TU/GA/AD/GB) use nvidia-open 615. 'chwd --list-installed' shows the installed profile. NVIDIA modules in the initramfs break resume from hibernation (open-gpu-kernel-modules #1343); /etc/mkinitcpio.conf.d/99-node-no-nvidia-early.conf removes them. The images Limine boots are the module_path lines of /boot/limine.conf (/boot/<machine-id>/<kernel>/initramfs; /boot/initramfs-*.img are stale installer copies nothing boots, limine_history/ holds snapshot copies; sections of another install on the same ESP carry another machine-id and are not checked). By hand: lsinitcpio /boot/<machine-id>/linux-cachyos/initramfs | grep -cE '/nvidia[^/]*\.ko' must print 0 (a plain 'grep nvidia' never comes back empty: the kms hook adds nouveau's firmware under usr/lib/firmware/nvidia/, and nvidia-utils' modprobe.d files stay). 580xx (Pascal) needs NVreg_PreserveVideoMemoryAllocations=1 and nvidia-suspend/hibernate/resume enabled. nvidia-powerd (Dynamic Boost: Ampere+ GPU and Comet Lake+ chipset only) is disabled on Turing and older. 'nvidia-smi' must work after a reboot.

7-ai

  1. 20rocmAMD: ROCm HIP SDK + OpenCL [Strix Halo or INSTALL_ROCM=1]applies

    ROCm HIP SDK + OpenCL from Arch extra, render/video groups

    Installs

    • repo rocm-hip-sdk rocm-hip-runtime hip-runtime-amd rocminfo rocm-smi-lib rocm-opencl-runtime clinfo

    Adds lines

    • /etc/ld.so.conf.d/rocm.conf gets /opt/rocm/lib after a check on the node

    Groups

    • render + <NODE_USER> after a check on the node
    • video + <NODE_USER> after a check on the node

    Verify checks (after the reboot)

    • OK rocminfo sees <printf output>
    • OK /dev/kfd present

    Troubleshooting hint

    rocminfo needs the render group: re-login (CachyOS has no sg/newgrp) or 'rocminfo'. No gfx1151 in rocminfo: 'dmesg | grep -i amdgpu'. If rocminfo segfaults on 7.2.x, boot the LTS kernel and compare.

  2. 21aidirsAI folder layout under /srv/aiapplies

    one folder for everything AI: /srv/ai (models, venvs, apps, data, caches), group ai, ~/ai link, README

    Writes

    • /srv/ai/README.md 664

    Groups

    • creates ai after a check on the node
    • ai + node user, ollama after a check on the node

    Verify checks (after the reboot)

    • OK <n> AI folders under /srv/ai: group ai, setgid
    • OK <NODE_USER> in group ai (active after a re-login)
    • OK ~/ai -> /srv/ai
    • OK /srv/ai/README.md present

    Settings it reads

    • INSTALL_DOCKER=1
    • AI_ROOT=/srv/ai
    • DOCKER_DATA_ROOT=/srv/docker

    Troubleshooting hint

    Everything AI lives under AI_ROOT (default /srv/ai; CachyOS mounts btrfs subvolume @srv at /srv, outside snapper's snapshots of /). Group ai (you + the ollama user) owns it: folders are mode 2775 (setgid: new files get group ai) with a default ACL group:ai:rwx; the membership is active after a re-login ('id' lists ai). Check: ls -ld /srv/ai /srv/ai/*; getfacl /srv/ai. ~/ai is a link to it unless ~/ai already held files (then a warning says how to switch). AI_ROOT's parent missing or read-only: ~/ai is used instead. README.md in the folder maps the tree; a hand-edited README is never overwritten.

  3. 22aienvAI environment + Ollama settingsapplies

    AI environment for sh/bash, fish and the desktop session + Ollama service settings for this GPU

    Writes

    • /etc/profile.d/node-ai.sh 644
    • /etc/fish/conf.d/node-ai.fish 644
    • /etc/environment.d/90-node-ai.conf 644
    • /etc/systemd/system/ollama.service.d/node.conf 644 written by node-setup.sh (backend: $OLLAMA_BACKEND_EFF) if INSTALL_OLLAMA = 1

    Settings it reads

    • INSTALL_OLLAMA=1
    • AI_ROOT=/srv/ai

    Troubleshooting hint

    Writes the same variables to /etc/profile.d/node-ai.sh (sh/bash), /etc/fish/conf.d/node-ai.fish (fish, the CachyOS login shell, does not read profile.d) and /etc/environment.d/90-node-ai.conf (systemd user manager -> desktop session), plus the Ollama drop-in /etc/systemd/system/ollama.service.d/node.conf (OLLAMA_MODELS in the AI folder; a running Ollama is restarted only when that file changed). Re-login, or 'source /etc/profile.d/node-ai.sh' (bash) / 'source /etc/fish/conf.d/node-ai.fish' (fish).

8-ai

  1. 23aitoolsOllama, Vulkan, Docker, uv, monitorsapplies

    Ollama (backend chosen for this GPU), Vulkan drivers, Docker (data in /srv/docker, + NVIDIA toolkit), uv, monitors

    Installs

    • repo uv python python-pip cmake ninja clang lld git-lfs btop nvtop vulkan-tools vulkan-icd-loader
    • repo vulkan-radeon mesa radeontop if HAS_AMD_GPU = 1
    • repo, if available amdgpu_top if HAS_AMD_GPU = 1
    • repo vulkan-intel intel-gpu-tools if HAS_INTEL_GPU = 1
    • repo, if available distrobox podman
    • repo, if available lemonade-server if INSTALL_LEMONADE = 1
    • repo docker docker-compose docker-buildx if INSTALL_DOCKER = 1
    • repo nvidia-container-toolkit if INSTALL_DOCKER = 1 and HAS_NVIDIA = 1 and NVIDIA_CONTAINER = 1
    • repo ollama ollama-vulkan if INSTALL_OLLAMA = 1 and OLLAMA_BACKEND_EFF = vulkan
    • repo ollama ollama-rocm if INSTALL_OLLAMA = 1 and OLLAMA_BACKEND_EFF = rocm
    • repo ollama ollama-vulkan ollama-rocm if INSTALL_OLLAMA = 1 and OLLAMA_BACKEND_EFF = both
    • repo ollama ollama-cuda if INSTALL_OLLAMA = 1 and OLLAMA_BACKEND_EFF = cuda
    • repo ollama if INSTALL_OLLAMA = 1 and OLLAMA_BACKEND_EFF = cpu

    Writes

    • /etc/docker/daemon.json 644 if INSTALL_DOCKER = 1, after a check on the node

    Services

    • docker.service enable --now if INSTALL_DOCKER = 1
    • ollama.service enable --now if INSTALL_OLLAMA = 1

    Groups

    • docker + <NODE_USER> if INSTALL_DOCKER = 1, after a check on the node
    • render + ollama if INSTALL_OLLAMA = 1, after a check on the node
    • video + ollama if INSTALL_OLLAMA = 1, after a check on the node
    • creates ai if INSTALL_OLLAMA = 1, after a check on the node
    • ai + node user, ollama if INSTALL_OLLAMA = 1, after a check on the node

    Verify checks (after the reboot)

    • OK docker daemon answers if INSTALL_DOCKER = 1, after a check on the node
    • OK Docker data-root <droot> if INSTALL_DOCKER = 1 and DOCKER_DATA_ROOT is set, after a check on the node
    • OK ollama models in <md> if INSTALL_OLLAMA = 1, after a check on the node
    • OK ollama API answers: <curl output> if INSTALL_OLLAMA = 1, after a check on the node
    • OK ollama found the GPU if INSTALL_OLLAMA = 1 and OLLAMA_BACKEND_EFF != cpu, after a check on the node
    • OK uv <uv output>

    Settings it reads

    • INSTALL_OLLAMA=1
    • INSTALL_DOCKER=1
    • INSTALL_LEMONADE=0
    • NVIDIA_CONTAINER=1
    • AI_ROOT=/srv/ai
    • DOCKER_DATA_ROOT=/srv/docker

    Troubleshooting hint

    Docker group needs re-login. ollama.service is restarted only when its backend packages, its groups or its model folder changed (loaded models stay loaded on a re-run). Ollama: 'journalctl -u ollama -b | grep -iE "inference compute|library=|dropping"'. Ollama skips integrated GPUs unless OLLAMA_IGPU_ENABLE=1 (set on Strix Halo, or everywhere with OLLAMA_IGPU=1). Ollama models: 'systemctl show ollama -p Environment' shows OLLAMA_MODELS; old models in /var/lib/ollama are reported with a move command, never moved. Docker data: data-root in /etc/docker/daemon.json is set only before Docker's first start ('docker info | grep "Docker Root Dir"'); existing /var/lib/docker data is never moved (the warning has the commands).

  2. 24pytorchPyTorch for this GPU (venv + smoke test)applies

    PyTorch in /srv/ai/venvs/torch from the right wheel index for this GPU, with a smoke test

    Python wheels

    • torch[device-gfx1151] torchvision[device-gfx1151] torchaudio from https://stable.repo.amd.com/rocm/whl-next/ if PYTORCH_FLAVOR = rocm-gfx1151
    • torch torchvision torchaudio from https://download.pytorch.org/whl/cu130 if PYTORCH_FLAVOR = cu130
    • torch torchvision torchaudio from https://download.pytorch.org/whl/cu126 if PYTORCH_FLAVOR = cu126

    Writes

    • /usr/local/bin/node-torch-check 755

    Verify checks (after the reboot)

    • OK torch sees the GPU

    Settings it reads

    • AI_ROOT=/srv/ai

    Troubleshooting hint

    venv: /srv/ai/venvs/torch (AI_ROOT; up to v2.2 it was ~/ai/venv, an old one is left in place). Strix Halo: AMD TheRock index torch[device-gfx1151]. NVIDIA Turing+: download.pytorch.org/whl/cu130. Pascal: whl/cu126 (last CUDA line with sm_6x). If torch.cuda.is_available is False: check the driver (nvidia-smi / rocminfo), groups, then reboot.

9-sleep

  1. 25hibernateHibernate to swap partition or fileapplies

    hibernate to swap (partition, or a btrfs swap file it creates); resume=, model unload; no suspend

    Writes

    • /etc/systemd/sleep.conf.d/node.conf 644 written by node-setup.sh
    • /usr/local/bin/node-ollama-unload 755 written by node-setup.sh - ask Ollama to unload every loaded model (frees pinned GPU memory before hibernate)
    • /etc/systemd/system/node-ollama-unload.service 644 written by node-setup.sh - runs before the system (and the NVIDIA driver) goes to sleep
    • /etc/systemd/system/node-ollama-resume.service 644 written by node-setup.sh - runs after resume (and after nvidia-resume on the 580xx driver)

    Adds lines

    • /etc/fstab gets UUID=<uuid> /swap btrfs subvol=/@swap,defaults,noatime 0 0 after a check on the node
    • /etc/fstab gets UUID=<uuid> none swap defaults 0 0 after a check on the node
    • /etc/fstab gets <SWAP_PATH> none swap defaults 0 0 after a check on the node

    Services

    • node-ollama-unload.service enable
    • node-ollama-resume.service enable

    Kernel parameters

    • resume=UUID=<swap> [resume_offset=<first page of the swap file>]

    Runs

    • btrfs subvolume create <top>/@swap after a check on the node
    • btrfs filesystem mkswapfile --size <gib>g --uuid clear /swap/swapfile after a check on the node
    • swapon <SWAP_PATH> after a check on the node

    Verify checks (after the reboot)

    • OK hibernate target: swap file <SWAP_PATH> (filesystem UUID=<uuid>, starts at page <offset>)
    • OK <p> active in cmdline
    • OK /sys/power/resume = <cat output>
    • OK /sys/power/resume_offset = <offset> (matches <SWAP_PATH>)
    • OK kernel lockdown none
    • OK model-unload before hibernate: enabled
    • OK booted initramfs images hold no NVIDIA modules (resume-safe) if HAS_NVIDIA = 1, after a check on the node

    Troubleshooting hint

    Target = the largest active non-zram swap (partition or file), >= RAM/0.98. Partition: resume=UUID=<swap UUID> (Calamares adds it when it formatted the swap in the same run; this step adds it if missing). File: resume=UUID=<filesystem UUID> resume_offset=<'btrfs inspect-internal map-swapfile -r FILE' on btrfs, else physical_offset of extent 0 in 'filefrag -v FILE'>. No disk swap + btrfs /: creates subvolume @swap (outside snapper's snapshots of @), mounts it at /swap (fstab), 'btrfs filesystem mkswapfile' /swap/swapfile, fstab, swapon; needs size + 10 GiB free on /. Check: swapon --show; cat /sys/power/resume /sys/power/resume_offset. CachyOS initramfs uses the systemd hook, so no 'resume' hook is needed. node-ollama-unload.service / node-ollama-resume.service run around hibernate (ordered against nvidia-hibernate / nvidia-resume). Razer: if a hibernate stalls ('journalctl -b 0 | grep -iE "Freezing of tasks failed|nvidia-gpu|ucsi"'), blacklist i2c_nvidia_gpu and ucsi_ccg in /etc/modprobe.d/ and run 'limine-mkinitcpio' (Arch wiki Razer_Blade). NVIDIA #1378 (driver 595, S3 suspend, KWin losing outputs) is related, unverified for 615 + hibernate. Test with 'rtcwake -m no' + 'systemctl hibernate', never rtcwake's own disk mode (it skipes systemd's sleep units, so the model unload would not run).

10-headless

  1. 26headlessHeadless: no sleep/lock/blanking, low batteryapplies

    never suspends (logind, sleep.conf.d, Plasma); no screen lock or blanking on a headless node; laptops hibernate at 5 % battery (UPower)

    Writes

    • /etc/systemd/logind.conf.d/node.conf 644 written by node-setup.sh - headless node: lid/folio and idle never suspend it
    • /etc/systemd/sleep.conf.d/node.conf 644 written by node-setup.sh
    • /etc/UPower/UPower.conf.d/90-node.conf 644 Oct 2026: always-on laptop on AC; on a long power outage hibernate at 5 % (warn at 8 %). after a check on the node
    • /etc/plasmalogin.conf.d/node-autologin.conf 644 written by node-setup.sh (Arch wiki: Plasma Login Manager) if AUTOLOGIN = 1, after a check on the node
    • /etc/sddm.conf.d/node-autologin.conf 644 written by node-setup.sh if AUTOLOGIN = 1, after a check on the node
    • /etc/xdg/kscreenlockerrc 644 written by node-setup.sh - lock the auto-logged-in session immediately if AUTOLOGIN = 1 and AUTOLOGIN_LOCK = 1

    Verify checks (after the reboot)

    • OK Plasma: no idle sleep or lid action (AC, Battery, LowBattery)
    • OK suspend disabled in /etc/systemd/sleep.conf.d/node.conf
    • OK low battery: hibernate at 5 % (/etc/UPower/UPower.conf.d/90-node.conf); UPower says: <upower output>

    Settings it reads

    • AUTOLOGIN=0
    • AUTOLOGIN_LOCK=0

    Troubleshooting hint

    Writes /etc/systemd/logind.conf.d/node.conf (lid and idle ignored; logind is reloaded, never restarted: a restart can end the Plasma session), /etc/systemd/sleep.conf.d/node.conf (no suspend; the same file the hibernate step writes), ~/.config/powerdevilrc (LidAction/AutoSuspendAction=0 for AC, Battery, LowBattery) and, on laptops, /etc/UPower/UPower.conf.d/90-node.conf (hibernate at 5 % battery; PowerOff while hibernation is not available; 'upower -d | grep critical-action'). Autologin: an existing plasma-login-manager / sddm drop-in is reported and kept; AUTOLOGIN=1 writes one, AUTOLOGIN_LOCK=1 also locks that session at every login.

10-disk

  1. 27maintenanceDisk watch: smartd + btrfs scrub (opt-in) [opt-in]opt-in: ENABLE_SMARTD=1 and/or ENABLE_SCRUB=1 (each enables a scheduled job, which needs the owner's yes)n/a

    smartd for the system NVMe + a monthly btrfs scrub (ENABLE_SMARTD=1 / ENABLE_SCRUB=1)

    Installs

    • repo smartmontools if ENABLE_SMARTD = 1

    Writes

    • /etc/smartd.conf 644 if ENABLE_SMARTD = 1, after a check on the node

    Services

    • smartd.service enable --now if ENABLE_SMARTD = 1
    • btrfs-scrub@-.timer enable --now if ENABLE_SCRUB = 1, after a check on the node

    Verify checks (after the reboot)

    • OK smartd active: <grep output> if ENABLE_SMARTD = 1, after a check on the node
    • OK monthly btrfs scrub timer enabled ('btrfs scrub status /') if ENABLE_SCRUB = 1, after a check on the node

    Settings it reads

    • ENABLE_SMARTD=0
    • ENABLE_SCRUB=0

    Troubleshooting hint

    Opt-in, each part enables a scheduled job. ENABLE_SMARTD=1: smartmontools; DEVICESCAN in /etc/smartd.conf becomes '/dev/nvmeN -a -W 0,70,80 -s <weekly short self-test>' for the system NVMe (a .bak copy is kept beside it; drop '-s ...' if smartd logs self-test errors: NVMe self-tests are experimental in smartmontools 7.5); smartd enabled; 'journalctl -u smartd -b'. ENABLE_SCRUB=1: btrfs-scrub@-.timer (monthly, idle I/O priority); 'btrfs scrub status /'.

11-done

  1. 28summaryFinal reportapplies

    final report, reboot advice, "needs the owner" list

    Troubleshooting hint

    Nothing to fix here; it only prints.

What the CLI prints

bash node-setup.sh --list: rendered from the step table and the applies_* results (checked against the script's own output for the default knobs), not captured on a node.

preflight    0-preflight   Preflight checks + hardware detection          pending
update       1-base        Full system update + base tools + paru         pending
lts          1-base        LTS kernel as fallback                         pending
ssh          2-remote      SSH daemon, hardened                           pending
firewall     2-remote      Firewall (ufw): allow SSH + tailnet            pending
tailscale    2-remote      Tailscale daemon                               pending
chrome       3-apps        Google Chrome (AUR)                            pending
asusctl      4-vendor      ASUS: asusctl + asusd (sole profile owner)     pending
fans         4-vendor      ASUS: fan curves to max                        pending
ppt          4-vendor      ASUS: power limits to board maximum            pending
razer        4-vendor      Razer: battery report, fans + power (opt-in)   n/a (not a Razer)
perfprofile  5-power       Performance power profile                      pending
powercap     5-power       Razer: CPU package power cap (PL1/PL2)         n/a (not a Razer)
cpu          5-power       ASUS: CPU governor/EPP/boost performance       pending
zram         5-power       zram size cap (Strix Halo only)                pending
quirks       5-power       Quirks: Wi-Fi power save, iwlwifi, MT7925      pending
kparams      5-power       EXTRA_KERNEL_PARAMS on the kernel cmdline      n/a (EXTRA_KERNEL_PARAMS is empty)
gpumem       6-gpu         Strix Halo: iGPU unified-memory kernel param   pending
nvidia       6-gpu         NVIDIA: driver branch, hibernate-safe boot, CUDA n/a (no NVIDIA GPU)
rocm         7-ai          AMD: ROCm HIP SDK + OpenCL                     pending
aidirs       7-ai          AI folder layout under /srv/ai                 pending
aienv        7-ai          AI environment + Ollama settings               pending
aitools      8-ai          Ollama, Vulkan, Docker, uv, monitors           pending
pytorch      8-ai          PyTorch for this GPU (venv + smoke test)       pending
hibernate    9-sleep       Hibernate to swap partition or file            pending
headless     10-headless   Headless: no sleep/lock/blanking, low battery  pending
maintenance  10-disk       Disk watch: smartd + btrfs scrub (opt-in)      n/a (opt-in: ENABLE_SMARTD=1 and/or ENABLE_SCRUB=1 (each enables a scheduled job, which needs the owner's yes))
summary      11-done       Final report                                   pending

Test harness

333/333

offline checks passing, 0 failing (Git Bash on a Windows PC, Oct 2026)

1,360 lines, 29 sections, 334 check calls; 1 sits inside an if (the jq merge test) and is skipped when jq is not installed, so the run counts 333. 10 fake machines: gz302, rz-turing, rz-pascal, rz-boardonly, desk, intel-only, asus-desk, asus-rog, amd-mini, amd-dgpu.

Harness sections in file order, with the number of check calls in each.
#SectionChecksShare
1every step has a function, a hint, and step ids are unique2
2bootloader cmdline editing: Limine (CachyOS default) = a drop-in the script owns18
3dry-run changes nothing2
3bLimine migration: the v2.1 inline line (the bug) and the drop-in made by hand on the Strix Halo node11
4systemd-boot (sdboot-manage): exactly the v2.1 marker-line behaviour4
5GRUB (single-quoted default line as CachyOS writes it)2
6write_root_file / ensure_root_line idempotency6
7failure path produces the AI-ready report and a non-zero status5
8a step whose last test is false still passes (set -e trap regression)2
9CLI entry points (executed, not sourced)3
10the hibernate units and the unload script5
11review fixes (v1.2)14
12hardware detection on fake machines (DMI dir, cpuinfo, lspci, power_supply)48
13hibernate target: swap partition, swap file, or a btrfs swap file made on a zram-only install24
15AI folders (v2.3): aidirs, the three env files, Ollama drop-in + model folder, Docker data-root, venv path49
16Qt/KDE skew guard after -Syu (v2.4): the Oct 2026 black screen on the Strix Halo node12
17powercap (v2.5, Razer): PL1 45 W / 28 s, PL2 65 W in both RAPL zones; boot unit + AC udev rule20
18Wi-Fi quirks (v2.5): power save off, iwlwifi shutdown hook, AX200 note, autoconnect-retries 018
19headless (v2.5): sleep.conf from both steps, Plasma lock/blank keys, UPower low battery, no DDC/CI21
20zram (v2.5): cap on Strix Halo only, no swappiness file, verify warns on mismatches7
21Razer step (v2.5): battery report; razer-control-revived only with RAZER_CONTROL=1, and then it warns, never dies9
22kparams (v2.5): EXTRA_KERNEL_PARAMS on every machine, in the managed drop-in7
23maintenance (v2.5, opt-in): smartd for the system NVMe, monthly btrfs scrub6
24tailscale key expiry, ssh note, failure-report filter, hibernate note + check, Wi-Fi-only reboot wording20
25nvidia step (v2.5): nvidia-powerd off on Turing; an ASUS laptop with Turing+ gets nvidia-open for vanilla 'linux'3
26thermald on the Razer (v2.5): off by default; INSTALL_THERMALD=1 runs a 60 s load check that warns on a drop5
27the real memory-module parser (v2.5 review: the tests stubbed it away entirely)5
28root is refused for every run that executes or verifies steps (v2.5 review: --only / --from skipped preflight)2
14file hygiene4

What real hardware taught

Oct 2026

The bootloader read the config file literally

The first version appended kernel parameters to the installer's Limine line with quotes and a trailing comment, the way a shell file would allow. limine-entry-tool is not a shell: it copied the quotes and the comment into the kernel command line. The script now owns one drop-in file with a single unquoted line and leaves the installer's line untouched; the test harness checks for quotes and '#' on that line.

CachyOS wiki: boot manager configuration

Oct 2026

A reviewer was right, the verifier was wrong

A boot unit had both WantedBy=multi-user.target and After=multi-user.target. A review agent flagged it as an ordering cycle; a verifier agent dismissed the finding. On the first boot systemd broke the cycle by deleting the start job, so the CPU performance unit never ran. The unit lost the After= line and the harness now fails any unit that has both.

Measured on the node; no public source

Oct 2026

A routine update left a black login screen

A full update mixed Arch's newer qt6-base with Qt modules and KDE packages that the CachyOS repositories had built against the previous Qt. Plasma's binaries failed with undefined Qt private-API symbols, so the login screen stayed black while SSH kept working. The update step now runs 'ldd -r' on the Plasma binaries and installs the lagging packages from Arch extra when they fail to load.

ldd(1)

Oct 2026

Wi-Fi that only a cold boot could fix

The Razer's Intel AX200 card failed to start after warm reboots (iwlwifi probe error -110); a PCI reset and another warm reboot did not help, a full power-off with the charger unplugged did. The script now installs a shutdown hook that unbinds the card before every reboot or power-off, and the node then passed 3 warm reboots in a row.

systemd-shutdown(8): shutdown hooks

Oct 2026

Wi-Fi power saving was most of the latency

On the 128 GB node, ping over Wi-Fi sat at roughly 90-120 ms. Turning NetworkManager's Wi-Fi power saving off brought it to 2-6 ms. The quirks step now writes that setting on every node with a wireless card and turns power saving off for the running connection too.

NetworkManager settings: wifi.powersave

Oct 2026

NVIDIA in early boot breaks resume

NVIDIA kernel modules inside the initramfs make resume from hibernation fail, and the hardware-detection tool puts them there. The nvidia step adds a later mkinitcpio drop-in that filters them out and survives the tool's rewrites; the verify step reads which images the bootloader actually boots and checks those.

NVIDIA open-gpu-kernel-modules #1343

Oct 2026

Hibernating with 118 GiB pinned for the GPU

A community report described a 128 GB Strix Halo laptop freezing while hibernating with large GPU allocations. The hibernate step installs a unit that asks Ollama to unload every model before the system sleeps and one that restores the service after resume. The second hibernate test passed with the GPU pool at 118 GiB and Ollama running.

CachyOS forum: hibernation on Strix Halo

Oct 2026

A power cap that can only lower a limit

The 2019 Blade's firmware sets the CPU's long-term power limit above Intel's 45 W rating. The powercap step caps it at 45 W / 65 W, but the same defaults on a 15 W U-series model would raise its limits. The step is therefore n/a where the cap is above the CPU's TDP, and it never raises a limit that is already lower.

Linux kernel docs: power capping framework

All settings and their defaults

Show the 42 settings
SettingDefaultScript comment
SSH_PORT22
SSH_AUTHORIZED_KEYS_URL(empty)e.g. https://github.com/<user>.keys
SSH_AUTHORIZED_KEYS_FILE$SCRIPT_DIR/authorized_keysfile beside this script
INSTALL_TAILSCALE1
INSTALL_CHROME1
INSTALL_LTS_KERNEL1
QT_SKEW_FIX1update step: Qt modules / Qt-KDE packages the CachyOS repos hold back behind Arch are installed from extra/ (1), or only reported (0)
INSTALL_OLLAMA1
OLLAMA_BACKEND knobautoauto | vulkan | rocm | both | cuda | cpu
OLLAMA_IGPU01 = let Ollama use an integrated AMD/Intel GPU (Strix Halo always does); benchmark first
INSTALL_DOCKER1
INSTALL_PYTORCH knobautoauto = only where a supported GPU exists | 1 | 0
INSTALL_ROCM knobautoauto = Strix Halo only | 1 = any AMD GPU | 0
INSTALL_CUDA_TOOLKITautoauto = Turing+ only (Arch cuda 13 dropped Pascal) | 1 | 0
INSTALL_LEMONADE0AMD Lemonade SDK server (Arch extra: lemonade-server)
INSTALL_THERMALDautoauto = Intel laptops except Razer (EC-managed; thermald would reset the powercap step's PL1 to the firmware's 60 W); 1 = also there; 0 = never
NVIDIA_PERSISTENCE1keep the NVIDIA GPU initialised (always-on node; disables RTD3)
NVIDIA_CONTAINER1nvidia-container-toolkit for Docker GPU access
TTM_PAGES_LIMIT_GIBautoauto = min(120, RAM - 6) GiB the GPU may pin. 1 GiB = 262144 pages
GTT_LEGACY01 = also set amdgpu.gttsize (deprecated on 6.18+, dmesg warns; harmless)
IOMMU_OFF01 = amd_iommu=off: community llama.cpp prompt speed +5-12%, generation ~+2%; AMD: not recommended; no NPU, no DMA protection
EXTRA_KERNEL_PARAMS(empty)any machine (kparams step), e.g. reboot=pci; kept in the same managed place
PPT_MAX11 = push ppt_pl1_spl/pl2_sppt/pl3_fppt to the board maximum (GZ302EA: 80/92/93 W)
FAN_CURVE30c:40%,40c:50%,50c:65%,60c:80%,70c:95%,80c:100%,90c:100%,100c:100%8 points, non-decreasing
FAN_PROFILEPerformance
WIFI_ASPM_FIX1options mt7925e disable_aspm=1 (community stability fix) where an MT7925 exists
RAZER_CONTROL00 = fans on the EC's automatic control, firmware power mode (default since v2.5, hardware audit); 1 = install razer-control-revived (AUR, community)
RAZER_POWER_MODE1RAZER_CONTROL=1 only: 0 Balanced, 1 Gaming, 2 Creator (some models), 3 Silent
RAZER_FAN_RPM0RAZER_CONTROL=1 only: 0 = automatic (EC); a fixed RPM on AC is 3500-5000, 3500-5300 on the 2019 Advanced (0245). No tachometer: it is a setpoint
RAZER_POWERCAP knob1powercap step: cap the CPU package power (the 2019 Advanced's BIOS/EC sets PL1 60 W / PL2 80 W, above Intel's 45 W TDP); 0 = leave the firmware limits. A cap only lowers: a firmware limit already below it is kept, and the step is n/a where RAZER_PL1_W is above the CPU's TDP (15 W U-series Stealth 13)
RAZER_PL1_W45long-term limit PL1 in W (Intel TDP of the i7-9750H); 0 = powercap step off
RAZER_PL1_WINDOW_US28000000PL1 time window in us (the hardware rounds it: 28 s reads back 27983872)
RAZER_PL2_W65short-term limit PL2 in W (a judgment call below the BIOS 80 W); 0 = step off
ZRAM_SIZEautoauto = min(ram / 8, 16384) on Strix Halo (the iGPU's memory comes out of RAM), elsewhere CachyOS's default 'ram' (no file written); or a zram-generator expression for any machine. Swappiness is left to cachyos-settings (150)
HIBERNATE knob1
SWAPFILE_GIB0hibernate step, only when there is no disk swap and / is btrfs: size of the swap file it creates in subvolume @swap (0 = auto from RAM: 8->12, 16->20, 32->36, 64->72, 128->140 GiB)
AUTOLOGIN01 = auto-login the desktop (a drop-in for plasma-login-manager or sddm); 0 = add none (an autologin drop-in already there is reported, kept)
AUTOLOGIN_LOCK0
ENABLE_SMARTD knob01 = smartd watches the system NVMe (weekly short self-test, 70/80 C warnings)
ENABLE_SCRUB knob01 = monthly btrfs scrub of / at idle priority (btrfs-scrub@-.timer)
AI_ROOT/srv/aione tree for models, venvs, apps, data, caches (CachyOS: /srv = btrfs subvolume @srv, outside snapper's snapshots of /). Parent missing or read-only: ~/ai
DOCKER_DATA_ROOT/srv/dockerDocker images/containers/volumes ("data-root" in daemon.json, set before Docker's first start only; existing data is never moved). Empty = Docker's /var/lib/docker

Parsed from the script: 35 file writes and 12 service enables reached from the steps (both equal to the call sites in the file), 25 verify functions, 13 applies gates.