# Hardware support

How Nomarchy enables CPUs, GPUs, laptops, firmware, and peripherals — and
what to do when your machine is not in the happy path.

**Minimum bar** (OpenGL, RAM, disk, UEFI): [`REQUIREMENTS.md`](REQUIREMENTS.md)
— read that before calling a machine “unsupported”; this file is the
enablement stack and quirk list.

> **Queue:** [`agent/BACKLOG.md`](../agent/BACKLOG.md) (PROPOSED › Hardware
> product). Product framing: [`VISION.md`](VISION.md) § A. Design history:
> [`ROADMAP.md`](ROADMAP.md). Docs map: [`README.md`](README.md).
> Migration: [`MIGRATION.md`](MIGRATION.md).

## 1. Architecture (three layers)

Nomarchy does **not** reimplement [nixos-hardware](https://github.com/NixOS/nixos-hardware).
It stacks:

```
┌─────────────────────────────────────────────────────────────────────┐
│  A. Always-on desktop floor                                         │
│     modules/nixos/default.nix + power.nix + audio/BT                │
│     redistributable firmware · fwupd · NM · PipeWire · PPD · ddcci  │
└───────────────────────────────┬─────────────────────────────────────┘
                                │
┌───────────────────────────────▼─────────────────────────────────────┐
│  B. nixos-hardware profiles  (via mkFlake hardwareProfile)          │
│     common-cpu-* · common-gpu-* · common-pc(-laptop|-ssd) · model   │
│     microcode · GPU media · fstrim · vendor/model quirks            │
└───────────────────────────────┬─────────────────────────────────────┘
                                │
┌───────────────────────────────▼─────────────────────────────────────┐
│  C. nomarchy.hardware.*  (modules/nixos/hardware.nix)               │
│     gap above commons: GuC · amd-pstate · VA-API env · fprintd ·    │
│     IR-webcam hide · ROCm/NPU/latestKernel · I2C/DDC/CI             │
└─────────────────────────────────────────────────────────────────────┘
```

| Layer | Who turns it on | Where it lives |
|-------|-----------------|----------------|
| **A** | Distro defaults (`mkDefault`) | Any machine importing `nomarchy.nixosModules.nomarchy` |
| **B** | Installer DMI/`lspci` → `hardwareProfile = [ … ]` | Downstream `flake.nix` (`mkFlake`) |
| **C** | Installer probes → `system.nix`, or hand-edit | Downstream `system.nix` |

`hardware-configuration.nix` (from `nixos-generate-config`) still owns
initrd modules, filesystems, and the usual generate-config flags. The
installer writes a real one; the template ships a placeholder you must
replace.

Unknown `hardwareProfile` names **fail at eval** with Levenshtein
suggestions (`lib.nix`) — never silently ignored.

## 2. What works without thinking about it

These ship on every Nomarchy system unless you override them:

| Capability | Default | Notes |
|------------|---------|--------|
| Wi‑Fi / BT firmware blobs | `hardware.enableRedistributableFirmware` | iwlwifi, ath, rtw, brcm, SOF, … |
| NetworkManager | on | Wi‑Fi UI: System › Network |
| PipeWire + WirePlumber | on | |
| Bluetooth + blueman | on | System › Bluetooth |
| power-profiles-daemon | on | Menu + Waybar; TLP via `nomarchy.system.power.backend = "tlp"` |
| External monitor brightness | `nomarchy.hardware.i2c.ddcci` **on** | DDC/CI → backlight devices |
| Firmware updates (LVFS) | `services.fwupd.enable` | **Never auto-flashes** — see §4 |
| SMART / UPower / pcscd (FIDO) | on | |
| earlyoom | on | Process-level OOM, not cgroup kill of the whole session |

**Not** default-on (need detect or uncomment): fprintd, GuC/HuC,
amd-pstate, ROCm, NPU, thermald, charge limit, snapper (installer enables
snapper), `latestKernel`.

## 3. Install path (best experience)

`nomarchy-install` sources `pkgs/nomarchy-install/hardware-db.sh` and:

1. Probes CPU vendor, GPUs (`lspci`), battery, SSD, fingerprint USB VIDs,
   RGB+IR webcam names, NPU PCI class.
2. Looks up DMI `sys_vendor` × `product_name` in a ~60-entry model table
   (Framework, Dell XPS/Latitude, ThinkPad, Surface, ASUS ROG, Apple T2,
   System76).
3. Emits `MODULE …` lines → `hardwareProfile` list in `flake.nix`.
4. Emits `NOMARCHY hardware.*=…` → active + commented blocks in
   `system.nix`.
5. Asks you to confirm profiles (or pick manually from the full
   nixos-hardware attr list baked into the ISO). Override with
   `NOMARCHY_HW=auto|none|"mod1 mod2"`.

**Live ISO** is intentionally *generic* (no model profile): broad GPU
modules as *available*, redistributable firmware on, no baked
`nomarchy.hardware.*`. Tuning happens at install time.

Out of scope for the installer today: aarch64 (Pi, Snapdragon X), Steam
Deck (Jovian), Apple Silicon (T2 Intel Macs only in the DB).

## 4. Firmware updates (deep dive)

### What we ship

```nix
# modules/nixos/default.nix
services.fwupd.enable = lib.mkDefault true;
```

fwupd talks to the [LVFS](https://fwupd.org/): UEFI capsule (BIOS), some
SSDs, docks, Thunderbolt controllers, peripherals. It **only refreshes
metadata** on its own. Applying an update is always an explicit user
action.

### What you do today (CLI)

```sh
# After first boot (and occasionally later):
fwupdmgr refresh          # optional; daemon often has metadata
fwupdmgr get-devices      # what LVFS can see
fwupdmgr get-updates      # pending
fwupdmgr update           # apply (may need reboot / battery / AC)
```

Disable on VMs/headless: `services.fwupd.enable = false;` in `system.nix`.

### Why this is under-discovered

| Present | Missing |
|---------|---------|
| Daemon + package | Doctor check (“updates available”) |
| README one-liner | Waybar / updates panel integration |
| **System ▸ Firmware menu** (`nomarchy-menu firmware`) | |
| MOTD + first-boot tip (#43) | |

**System ▸ Firmware** (shipped): a self-gated System-submenu row (present
whenever `fwupdmgr` is on PATH, i.e. `services.fwupd.enable`, default-on)
that opens a terminal and runs `fwupdmgr refresh` → `get-updates` →
confirm → `fwupdmgr update`. It never auto-flashes — `fwupdmgr update`
confirms each device and prompts for the reboot a capsule needs; pillar 1
(rock-stable) forbids silent BIOS writes.

**Hints (#43 / #73):** MOTD cheat-sheet line when `fwupdmgr` is on PATH.
Fingerprint / doctor tips follow the same pattern (fingerprint MOTD only
when `nomarchy.hardware.fingerprint.enable`). A one-shot toast in the
first graphical session also points at System › Firmware when `fwupdmgr`
is on PATH (`settings.hardwareHintsShown`, `nomarchy-first-boot`) — for
people who never read the MOTD.

**Still queued:** a Doctor “updates available” check, and
Waybar/updates-panel integration.

### Thunderbolt

Firmware for TB devices may appear via LVFS. There is **no** first-class
`services.hardware.bolt` / security-level UI in Nomarchy — use plain
NixOS options if you need bolt.

### Microcode / kernel firmware

- **CPU microcode:** from nixos-hardware `common-cpu-*` and
  `nixos-generate-config` once redistributable firmware is on — not a
  separate `nomarchy.*` toggle.
- **GPU GuC/HuC (Intel i915):** `nomarchy.hardware.intel.guc` →
  `i915.enable_guc=3`. Installer turns this **off** when the bound driver
  is `xe` (GuC is default there; the param is ignored).
- **`hardware.enableAllFirmware`:** not set. Oddball non-redistributable
  Wi‑Fi still needs a manual NixOS fix (or a broader policy decision).

## 5. Fingerprint (deep dive)

### Detect → enable

Installer scans USB vendor IDs common to libfprint (Goodix, Synaptics,
Elan, …) via `lsusb` or `/sys/bus/usb/.../idVendor`. On hit:

```nix
nomarchy.hardware.fingerprint.enable = true;  # services.fprintd
# nomarchy.hardware.fingerprint.pam = true;   # login + sudo (opt-in)
# nomarchy.hardware.fingerprint.parallel = false;  # sequential prompt instead
```

### Enroll (menu or CLI)

**Shipped #55:** System › Fingerprint (self-gated when `fprintd-list` is
on PATH) — a single **Fingerprint (on/off)** switch, plus Enroll / List /
Verify / Delete all (all usable while it's off — turning it on needs an
enrolled finger). The switch writes `settings.fingerprint.pam`, the one
state key behind *both* halves of "use my finger": login/sudo PAM here,
and the lock-screen unlock in `modules/home/idle.nix`. It runs
`nomarchy-fingerprint`, which sudos a system rebuild and then a home
switch, because the two halves live in different configurations.

It does **not** decide whether login prompts at all: auto-login skips the
greeter entirely, so this adds the finger to the prompts that actually
happen — sudo, the lock screen, and the greeter only when
[auto-login](#auto-login) is off.

**Hints (#73):** MOTD line when `fingerprint.enable` is on; first-boot
tip when `fprintd-list` is on PATH (`SUPER+M → System › Fingerprint` /
`fprintd-enroll`). No permanent nag without a reader. A one-shot toast in
the first graphical session also points at System › Fingerprint when
`fprintd-list` is on PATH (`settings.hardwareHintsShown`,
`nomarchy-first-boot`) — for people who never read the MOTD.

```sh
# CLI still works:
fprintd-enroll
fprintd-list "$USER"
nomarchy-fingerprint toggle     # or on | off | status — what the menu row runs
```

PAM stays opt-in on purpose: password-only remains the cautious default
until a finger is enrolled. Full enroll on a real reader is V3/hardware.

### Auto-login

`settings.greeter.autoLogin` (System › Auto-login, or `nomarchy-autologin
[toggle|on|off|status]`) decides whether boot goes straight to the desktop
or stops at the greeter. The installer seeds it ON for LUKS-encrypted
machines — the disk passphrase already gates the machine, so a greeter
password is a second prompt for the same thing — and leaves it off
without LUKS, where the greeter is the only thing between power-on and the
desktop. It's baked into greetd at system rebuild, so the change shows on
the next boot.

Auto-login and fingerprint are independent, and auto-login wins at boot:
with it on you are never asked for anything at startup, whatever the
fingerprint switch says. Turn auto-login off and the greeter asks — for a
password, or a password *or* finger when fingerprint is on.

Both are in-flake state rather than lines in `system.nix` on purpose: a
hand-set `nomarchy.system.greeter.autoLogin` (or
`nomarchy.hardware.fingerprint.pam`) outranks the state and pins the
setting, leaving the menu toggle unable to move it. That's the escape
hatch, not the default — leave those lines commented to use the menu.

### Parallel prompt (password *or* finger, whichever first)

With `fingerprint.pam` on, sudo/login/hyprlock show **one** prompt that
accepts either factor — type the password or touch the sensor
(2026-07-12; `pam-fprint-grosshack`, an fprintd fork, since stock PAM
can't express parallel factors). This is the default;
`fingerprint.parallel = false` restores stock pam_fprintd's sequential
wait-for-the-reader-then-password. Lockout safety is structural: the
module never validates passwords itself — a typed password is handed to
the normal `pam_unix` rule, and every failure (no reader, fprintd hung,
timeout) falls through to password, so finger auth can only ever *add* a
way in. Verify the wiring with
`grep pam_fprintd_grosshack /etc/pam.d/sudo`. Known cosmetic quirk: after
a fingerprint win a leftover password prompt may linger on the console.

### Doctor

`nomarchy-doctor` reports fprintd unit + enroll status when present.

## 6. NVIDIA (deep dive)

### What happens at install

```
lspci → NVIDIA  ⇒  MODULE common-gpu-nvidia
```

That is the **entire** Nomarchy surface. Unfree packages are allowed
(`allowUnfree = true` in the module and in `mkFlake`), so the proprietary
path from nixos-hardware can evaluate.

There is **no** `nomarchy.hardware.nvidia.*` for:

- hybrid / PRIME (Intel+NVIDIA, AMD+NVIDIA)
- open vs closed kernel module
- power management / udev / suspend quirks
- CUDA / container toolkit packages

### What a hybrid laptop user must do today

1. Confirm `common-gpu-nvidia` (and often `common-gpu-intel` or
   `common-gpu-amd`) are in `hardwareProfile`.
2. Read the relevant nixos-hardware module and [NixOS NVIDIA wiki](https://wiki.nixos.org/wiki/Nvidia).
3. Add plain NixOS options in `system.nix`, for example (illustrative —
   hardware-specific):

```nix
# Example only — verify against your generation and nixos-hardware module.
# hardware.nvidia.prime = { ... };
# hardware.nvidia.powerManagement.enable = true;
# hardware.nvidia.open = false;  # or true for open modules on newer cards
```

4. Rebuild the system. Home Manager does not own the driver.

### Live ISO note

The ISO lists `nouveau` among *available* initrd modules and does **not**
force-load every GPU driver (avoids multi-driver panics). Proprietary
NVIDIA on the live session is not the product focus; installed systems
use the profile.

**Product direction (shipped #59; first-class wrappers deferred for v1):**
when `common-gpu-nvidia` is in `hardwareProfile`, the installer emits a
**commented** `system.nix` block with PRIME / power / open-module pointers
(same pattern as ROCm / NPU comments). No `nomarchy.hardware.nvidia.*`
stack for v1 — hybrid PRIME remains wiki/plain NixOS until a maintainer
commits to hardware testing.

## 7. Day-2: “my laptop is mostly fine, make it fully fine”

| Goal | How | Rebuild? |
|------|-----|----------|
| Power profile (perf/balanced/saver) | System menu / Waybar | No (PPD D-Bus) |
| Charge limit 80% | Menu / CC — live sysfs + state; boot oneshot re-applies | No rebuild for live; rebuild bakes Nix option |
| Thermald (Intel) | Installer on for Intel laptops; else `power.thermal.enable` | System |
| Fingerprint enroll | `fprintd-enroll` | No |
| Fingerprint login | `fingerprint.pam = true` | System |
| ROCm / Intel compute | Uncomment opt-ins in `system.nix` | System (large) |
| NPU driver | Uncomment `npu` (+ often `latestKernel`) | System; userspace BYO |
| Newer kernel for brand-new silicon | `nomarchy.hardware.latestKernel = true` | System |
| Hide IR “dark camera” | Installer or `camera.hideIrSensor` | System |
| Firmware | `fwupdmgr update` | Reboot if capsule requires |
| Model quirks missing | Set better `hardwareProfile` (see §8) | System |
| OpenRGB / printing / Steam | `nomarchy.services.*` | System |

### Dual-sensor webcam / IR hide

Many dual-sensor modules (e.g. ThinkPad T14s) expose colour + IR as two
identically named “Integrated Camera” nodes. Picking the IR node yields a
black/dark greyscale frame — the classic “my webcam is dark” symptom.

`nomarchy.hardware.camera.hideIrSensor` (installer-on when RGB+IR names
are detected; overridable `irMatch`) drops the IR node from **WirePlumber’s
v4l2 monitor** so native PipeWire pickers only offer the colour camera.
The kernel `/dev/video*` node stays open, so Howdy-style face unlock still
works. Design history: [ROADMAP § Webcam](ROADMAP.md).

**What it does not cover:** apps that list cameras via **libcamera** or
the **xdg-desktop-portal** / **Flatpak** camera path still see both
sensors — a Flatpak Zoom (or similar) user can still pick the black IR
“camera”. That is intentional: a blanket libcamera disable would risk
external USB cams that need the libcamera path, and surgical internal-only
libcamera rules could not match early enough (only `device.api` binds
before the WirePlumber monitor rule).

**Workaround:** prefer the colour device in the picker (or any non-IR
name). Apps that default to the first v4l2 colour source without a picker
are fine.

**Further engineering (needs T14s-class hardware):** (b) a WirePlumber
*libcamera* monitor rule disabling GREY-only nodes; (c) a libcamera/udev
quirk at the libcamera layer. Neither is implemented — the recommended
path is document-only until those can be verified on real dual-sensor
hardware (see `agent/HARDWARE-QUEUE.md` › T14s).

Theme switches never touch drivers (Home Manager only).

## 8. Unsupported or unlisted machines

### Still good floor

Even with **no** DMI hit you usually get:

- `common-cpu-{intel,amd}`
- `common-gpu-{intel,amd,nvidia}` as applicable
- `common-pc` or `common-pc-laptop` + optional `common-pc-ssd`
- `nomarchy.hardware.intel` or `.amd` when vendor matches
- Layer A (firmware, audio, BT, PPD, fwupd)

You **lose** model-specific EC/suspend/keyboard/audio quirks that only
exist in a named nixos-hardware module.

### Pick a profile after install

1. List candidates:

   ```sh
   nix eval github:NixOS/nixos-hardware#nixosModules \
     --apply builtins.attrNames
   # or: the ISO ships hardware-modules.txt for the same list
   ```

2. Set in your flake (installer already uses a list):

   ```nix
   hardwareProfile = [
     "common-cpu-amd"
     "common-gpu-amd"
     "common-pc-laptop"
     "common-pc-ssd"
     "lenovo-thinkpad-t14-amd-gen3"  # if it matches
   ];
   ```

3. `sudo nixos-rebuild switch --flake ~/.nomarchy#default`

### Template / migration (no installer)

See [MIGRATION.md](MIGRATION.md): reuse `hardware-configuration.nix`, set
`hardwareProfile`, uncomment `nomarchy.hardware` / power in `system.nix`.
Post-install re-probe (**shipped #58**):

```sh
nomarchy-detect-hw        # human report + suggested snippets
nomarchy-detect-hw --raw  # MODULE / NOMARCHY / DETAIL protocol lines
```

Prints suggested `hardwareProfile` and `system.nix` lines; **does not**
rewrite the flake. Paste after review, then `sudo nixos-rebuild switch`.

## 9. Install-tested machines (hall of fame)

Models that have seen a real Nomarchy session (live ISO, install, or
day-to-day QA) — not a guarantee of every feature, just a public seed
list. **DB coverage** (hardware-db.sh) is broader; this table is the
smaller “someone actually sat at it” set.

| Model | DMI (`sys_vendor` × `product_name` / version) | Typical `hardwareProfile` | Last noted | Notes |
|-------|-----------------------------------------------|---------------------------|------------|-------|
| Lenovo ThinkPad T14s Gen 4 (AMD) | `LENOVO` × `21F8CTO1WW` / `ThinkPad T14s Gen 4` | common AMD laptop + SSD (plus `nomarchy.hardware.amd` / fingerprint / IR cam as probed) | 2026-07 | Maintainer day-to-day + HARDWARE-QUEUE (IR portal, fingerprint, NPU). Dual-sensor webcam is the motivating IR case. |
| Dell Latitude 5410 | Dell Latitude 5410 (Intel) | common Intel laptop + SSD (+ GuC when enabled) | 2026-07 | Primary Intel hardware-QA host (session, themes, polkit, bar). |
| Dell Latitude 5310 | Dell Latitude 5310 (Intel) | same family as 5410 | 2026-07 | Charge-limit / power QA sibling (see HARDWARE-QUEUE). |

### Send your DMI line

If you install Nomarchy on a machine and it works (or fails in an
interesting way), open a PR that:

1. Adds a row to the table above (model, DMI, profile, date, short notes).
2. Optionally adds a `HARDWARE_DB` line in
   `pkgs/nomarchy-install/hardware-db.sh` when nixos-hardware has a
   matching module (§10).

Quick DMI grab:

```sh
printf '%s | %s | %s\n' \
  "$(cat /sys/class/dmi/id/sys_vendor)" \
  "$(cat /sys/class/dmi/id/product_name)" \
  "$(cat /sys/class/dmi/id/product_version)"
nomarchy-detect-hw   # suggested MODULE / NOMARCHY lines
```

## 10. Adding your model to the distro

For contributors (and power users who will PR):

1. On the machine:

   ```sh
   cat /sys/class/dmi/id/sys_vendor
   cat /sys/class/dmi/id/product_name
   ```

2. Find a matching attr in
   [nixos-hardware](https://github.com/NixOS/nixos-hardware) (or add one
   upstream first).

3. Append to `pkgs/nomarchy-install/hardware-db.sh` in `HARDWARE_DB`:

   ```bash
   "VendorRegex|Product regex|nixos-hardware-module-name"
   ```

   First match wins — put specific lines above broad fallbacks.

4. Verify the name exists in the **pinned** nixos-hardware input
   (`mkFlake` will throw if not). A CI check that every DB name ∈
   `nixosModules` is queued so lock bumps cannot silently break installs.

5. Optional: note on-hardware QA steps in `agent/HARDWARE-QUEUE.md`.
   Install-tested status goes in the hall-of-fame table (§9).

## 11. Doctor and hardware health (current vs target)

**Today** (`nomarchy-doctor`): failed units, disk space, state.json
validity/git, generation age, snapper; hardware section self-gates per
machine (NetworkManager, audio sink, GPU smoke, fprintd, fwupd, charge
limit, battery health, hibernate/zram). All read-only; each ✖ prints one
fix command.

| Check | Pass condition | Suggested fix line |
|-------|----------------|--------------------|
| NetworkManager | device connected or wifi radio on | open System › Network |
| Audio sink | default sink exists | System › Audio |
| GPU accel | `glxinfo`/`vainfo` smoke (if installed) | check `hardwareProfile` / drivers |
| Fingerprint | if USB VID matched / fprintd unit | `fprintd-enroll` or enable option |
| fwupd | daemon active; optional “updates pending” warn | `fwupdmgr get-updates` |
| Battery threshold | if laptop + limit set, sysfs reports it | power docs / restart charge-limit unit |
| Battery health | cycle_count and/or charge\|energy_full÷design % when sysfs exports them | report-only (warn if &lt;70% of design) |
| First-boot pre-activate | installer log present + no HM generation | `home-manager switch --flake ~/.nomarchy -b bak` |
| Hibernate / zram | disk swap + resume=; zram active | docs/MIGRATION.md → Enabling hibernation |

## 12. Option quick reference

Full tables: [README § options](../README.md). Hardware-shaped surface:

| Option | Role |
|--------|------|
| `mkFlake.hardwareProfile` | nixos-hardware name or list |
| `nomarchy.hardware.intel.enable` / `.guc` / `.computeRuntime` | Intel gap layer |
| `nomarchy.hardware.amd.enable` / `.pstate` / `.vaapi` / `.rocm.*` | AMD gap layer |
| `nomarchy.hardware.fingerprint.enable` / `.pam` / `.parallel` | fprintd + PAM (parallel password-or-finger prompt by default) |
| `nomarchy.hardware.npu.enable` | in-kernel NPU only |
| `nomarchy.hardware.latestKernel` | `linuxPackages_latest` |
| `nomarchy.hardware.camera.hideIrSensor` / `.irMatch` | dual-sensor webcams (v4l2 only; §7) |
| `nomarchy.hardware.i2c.enable` / `.ddcci` | I2C + external backlight |
| `nomarchy.system.power.*` | PPD/TLP, laptop, thermald, charge limit |
| `nomarchy.system.bluetooth.enable` | BT stack |
| `services.fwupd.enable` | LVFS (native NixOS, default on) |

## 13. Ease summary

| Situation | Effort |
|-----------|--------|
| Known model in DB (Framework, many ThinkPads, …) | Low — install and go |
| Unknown modern Intel/AMD laptop | Low–medium — commons cover a lot |
| Firmware updates | Medium — CLI only until product work lands |
| Fingerprint for login | Medium — enroll CLI + PAM uncomment |
| Hybrid NVIDIA | High — plain NixOS/wiki territory |
| ROCm / NPU userspace | High — opt-in + expert |
| Contribute a new DMI line | Medium for someone who can PR |

## 14. Related files

| Path | Role |
|------|------|
| `modules/nixos/hardware.nix` | `nomarchy.hardware.*` |
| `modules/nixos/power.nix` | PPD/TLP, charge limit, thermald |
| `modules/nixos/default.nix` | firmware, fwupd, ddcci default |
| `pkgs/nomarchy-install/hardware-db.sh` | DMI DB + probes |
| `pkgs/nomarchy-install/nomarchy-install.sh` | writes flake + system.nix |
| `lib.nix` | `hardwareProfile` resolution |
| `templates/downstream/system.nix` | commented opt-ins |
| `agent/HARDWARE-QUEUE.md` | on-machine V3 checks |
