diff --git a/docs/superpowers/plans/2026-05-25-fingerprint-reader.md b/docs/superpowers/plans/2026-05-25-fingerprint-reader.md new file mode 100644 index 0000000..61e0b6c --- /dev/null +++ b/docs/superpowers/plans/2026-05-25-fingerprint-reader.md @@ -0,0 +1,302 @@ +# Framework 13 Fingerprint Reader Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Enable the Framework 13's Goodix fingerprint reader for `sudo` and `gtklock` via `fprintd` + a one-time idempotent PAM-edit script. + +**Architecture:** Three small repo changes plus one user-side runtime activity. Repo: add `fprintd` to `packages.txt`, ship a `scripts/enable-fingerprint.sh` bootstrap that adds `auth sufficient pam_fprintd.so` to two PAM files only if not already present, document the two manual user steps (enroll + run script) in README. Runtime: user runs `fprintd-enroll` once + `sudo bash scripts/enable-fingerprint.sh` once. + +**Tech Stack:** bash, `fprintd`/`libfprint`, PAM, Arch Linux. + +**Spec:** `docs/superpowers/specs/2026-05-25-fingerprint-reader-design.md` + +**Verification model:** Bash syntax checks for the new script + idempotency dry-run (run script's grep-guards against a copy and confirm they short-circuit). End-to-end functional check (touch reader → unlock) is interactive and live-only — controller verifies after install + manual enroll. + +--- + +## File Structure + +| File | Role | +|---|---| +| `packages.txt` | Add `fprintd` line (libfprint pulled as dep). | +| `scripts/enable-fingerprint.sh` | New bash bootstrap. For each of `/etc/pam.d/sudo` and `/etc/pam.d/gtklock`: backup + insert `auth sufficient pam_fprintd.so` before first existing `auth ` line if not already present. Requires sudo. | +| `README.md` | Append `### One-time fingerprint setup` subsection under `## Setup`. | + +No new niri config, no install.sh changes (the bootstrap is user-triggered like `enable-hibernation.sh`). + +--- + +## Task 1: Add `fprintd` to `packages.txt` + +After this task, fresh installs get `fprintd` (and `libfprint` as transitive dependency). The reader is unusable until enrollment + PAM wiring (later tasks). + +**Files:** +- Modify: `packages.txt` (add one line) + +- [ ] **Step 1: Add the package** + +The list isn't strictly alphabetical. Insert `fprintd` between two logical neighbours — after `polkit-gnome` (line 16) is a sensible "auth/security" cluster slot: + +Current lines 16-17: +``` +polkit-gnome +networkmanager +``` + +Becomes: +``` +polkit-gnome +fprintd +networkmanager +``` + +- [ ] **Step 2: Verify the entry is present exactly once** + +```bash +grep -c "^fprintd$" packages.txt +``` + +Expected: `1`. + +- [ ] **Step 3: Commit** + +```bash +git add packages.txt +git commit -m "packages: add fprintd for fingerprint reader support" +``` + +--- + +## Task 2: Write the bootstrap script + +After this task, `scripts/enable-fingerprint.sh` exists. Running it as root adds `auth sufficient pam_fprintd.so` to the two target PAM files. Re-runs are no-ops. + +**Files:** +- Create: `scripts/enable-fingerprint.sh` + +- [ ] **Step 1: Create the script** + +Write `scripts/enable-fingerprint.sh` with this exact content: + +```bash +#!/bin/bash +# enable-fingerprint.sh — wire pam_fprintd.so into sudo + gtklock auth stacks. +# Idempotent: re-runs are no-ops once the line is present. Run with sudo. +# Prerequisite: fprintd installed and at least one finger enrolled +# (run `fprintd-enroll` as your user first). + +set -euo pipefail + +if [ "$EUID" -ne 0 ]; then + echo "Run as root: sudo bash $0" >&2 + exit 1 +fi + +if ! command -v fprintd-enroll >/dev/null 2>&1; then + echo "ERROR: fprintd is not installed. Run install.sh first." >&2 + exit 1 +fi + +PAM_LINE="auth sufficient pam_fprintd.so" +TARGETS=(/etc/pam.d/sudo /etc/pam.d/gtklock) + +for f in "${TARGETS[@]}"; do + if [ ! -f "$f" ]; then + echo "ERROR: $f does not exist. Is the corresponding package installed?" >&2 + exit 1 + fi + + if grep -q "pam_fprintd" "$f"; then + echo "$f: pam_fprintd already present — skipping" + continue + fi + + backup="$f.bak.$(date +%s)" + cp -a "$f" "$backup" + echo "$f: backed up to $backup" + + # Insert PAM_LINE on a new line directly before the first `auth ` directive. + awk -v line="$PAM_LINE" ' + !inserted && /^auth[[:space:]]/ { print line; inserted = 1 } + { print } + ' "$f" > "$f.new" + + if ! grep -q "pam_fprintd" "$f.new"; then + echo "ERROR: failed to insert PAM line into $f (no auth directive found?). Restoring backup." >&2 + rm -f "$f.new" + exit 1 + fi + + mv "$f.new" "$f" + chmod --reference="$backup" "$f" + echo "$f: inserted '$PAM_LINE'" +done + +echo +echo "Done. Test with: sudo true (should prompt for fingerprint)" +echo "Or lock the screen (Mod+Shift+E) and touch the reader." +echo +echo "To revert: restore the .bak. files in /etc/pam.d/, or:" +echo " sudo sed -i '/pam_fprintd/d' ${TARGETS[*]}" +``` + +- [ ] **Step 2: Make it executable** + +```bash +chmod +x scripts/enable-fingerprint.sh +ls -la scripts/enable-fingerprint.sh +``` + +Expected: `-rwxr-xr-x` mode. + +- [ ] **Step 3: Syntax-check** + +```bash +bash -n scripts/enable-fingerprint.sh && echo "syntax ok" +``` + +Expected: `syntax ok`, exit 0. + +- [ ] **Step 4: Dry-run the EUID guard (no sudo, no side effects)** + +```bash +bash scripts/enable-fingerprint.sh 2>&1 +echo "exit=$?" +``` + +Expected: +``` +Run as root: sudo bash scripts/enable-fingerprint.sh +exit=1 +``` + +- [ ] **Step 5: Dry-run the idempotency / awk logic against a fixture (no sudo)** + +Create a fake PAM file in /tmp with an `auth` line, run the insertion logic by hand, and verify the line lands in the right place: + +```bash +cat > /tmp/fake-pam <<'EOF' +#%PAM-1.0 +auth include system-auth +account include system-auth +password include system-auth +session include system-auth +EOF + +# Pull the awk insertion block out and run it +awk -v line="auth sufficient pam_fprintd.so" ' + !inserted && /^auth[[:space:]]/ { print line; inserted = 1 } + { print } +' /tmp/fake-pam + +# Expected output: the new auth line appears on line 2, original 4 lines below it. +``` + +Then verify the idempotency grep works — running on a fixture that already has `pam_fprintd` should not duplicate: + +```bash +echo "auth sufficient pam_fprintd.so" >> /tmp/fake-pam +grep -q "pam_fprintd" /tmp/fake-pam && echo "idempotency guard triggers correctly" +``` + +Expected: `idempotency guard triggers correctly`. + +Cleanup: +```bash +rm /tmp/fake-pam +``` + +- [ ] **Step 6: Commit** + +```bash +git add scripts/enable-fingerprint.sh +git commit -m "scripts: add enable-fingerprint.sh (one-time PAM bootstrap)" +``` + +--- + +## Task 3: README — `### One-time fingerprint setup` subsection + +After this task, the README documents all three user-side steps (install run is implicit via packages.txt; enrollment + bootstrap script are explicit). + +**Files:** +- Modify: `README.md` (append a subsection after the existing "One-time NetworkManager activation") + +- [ ] **Step 1: Append the new subsection** + +Open `README.md`. The last subsection is `### One-time NetworkManager activation`. After its closing line, append: + +````markdown + +### One-time fingerprint setup + +After `install.sh` installs `fprintd`, enroll a finger and wire it into the sudo + lockscreen auth stacks: + +```bash +fprintd-enroll # interactive; touch the reader as prompted +sudo bash scripts/enable-fingerprint.sh # idempotent — safe to re-run +``` + +Test with `sudo true` (should prompt for fingerprint) or `Mod+Shift+E` to lock + touch the reader. + +To enroll additional fingers: `fprintd-enroll -f left-thumb` (or `right-thumb`, `left-index`, etc.). +```` + +(The outer fence above is four backticks because the block contains a triple-backtick code block — paste the inner content verbatim, no enclosing 4-backtick fence.) + +- [ ] **Step 2: Verify the section was added** + +```bash +grep -A 8 "One-time fingerprint setup" README.md +``` + +Expected: the new subsection prints, including both the `fprintd-enroll` and `sudo bash scripts/enable-fingerprint.sh` lines. + +- [ ] **Step 3: Commit** + +```bash +git add README.md +git commit -m "docs: document one-time fingerprint setup" +``` + +--- + +## Final verification (controller, post-merge) + +After all three task commits land: + +- [ ] **Install fprintd** + +```bash +bash install.sh +# OR direct: sudo pacman -S --needed fprintd +which fprintd-enroll fprintd-verify fprintd-list +``` + +- [ ] **Enroll a finger** (interactive, ~10 s): +```bash +fprintd-enroll +# Touch the reader as prompted (~5 swipes) +fprintd-list "$USER" +# Expected: lists enrolled fingers +``` + +- [ ] **Run the bootstrap script** + +```bash +sudo bash scripts/enable-fingerprint.sh +``` + +Expected output mentions backing up `/etc/pam.d/sudo` and `/etc/pam.d/gtklock` and inserting the line. Re-running it should print "pam_fprintd already present — skipping" for both. + +Confirm: +```bash +grep "pam_fprintd" /etc/pam.d/sudo /etc/pam.d/gtklock +``` +Expected: both files show the line near the top. + +- [ ] **Functional tests:** + +1. `sudo true` — should prompt for fingerprint; touch reader → no password requested. (Password fallback still works if you ignore the prompt or press Enter.) +2. `Mod+Shift+E` → lock screen → touch reader → unlocks without typing password. +3. `fprintd-verify` — touch reader → "verify-match" exits 0.