Add T490 hardware configuration and docs

- modules/t490.nix: TLP power management, CPU throttling (throttled), S3 sleep,
  remote Nix builders (cluster), fwupd, fprintd, fprintd, Syncthing, Tailscale,
  Intel VA-API, TrackPoint sensitivity, PipeWire defaults
- flake.nix: add nixos-hardware input, import T490 module + nixos-hardware
- docs/T490-HARDWARE.md: WiFi variants, graphics, audio, fingerprint, sleep guide
- docs/T490-BATTERY.md: TLP config, threshold strategy, real-world battery numbers
- docs/T490-OFFLOADING.md: local vs cluster split, remote builder setup
- MIGRATION.md: add T490 post-install steps (fwupd, fprint, remote build key)
- README.md: reflect new module and docs structure
This commit is contained in:
Scott Scoble 2026-05-14 17:40:04 -07:00
parent 6df0a20265
commit 5051dfdc8c
7 changed files with 364 additions and 3 deletions

View file

@ -88,6 +88,27 @@ curl -s http://localhost:3987/health # should return ok
# Discord: @mention Marcus in #speaker-site # Discord: @mention Marcus in #speaker-site
``` ```
### 8. T490 hardware setup
```bash
# Firmware updates (BIOS, EC, Thunderbolt, fingerprint reader)
fwupdmgr refresh && fwupdmgr update
# May need to disable Boot Order Lock in BIOS first
# Enroll fingerprint
fprintd-enroll
# Check sleep mode
cat /sys/power/mem_sleep
# Should show [deep] if S3 is available and configured
# Set up remote build SSH key
sudo ssh-keygen -t ed25519 -f /root/.ssh/remote-build -N ""
# Copy /root/.ssh/remote-build.pub to each cluster node's /root/.ssh/authorized_keys
# Set up Syncthing (pair with cluster nodes for vault sync)
systemctl --user enable --now syncthing
```
## Files to NOT transfer ## Files to NOT transfer
These contain caches, build artifacts, or macOS-specific junk: These contain caches, build artifacts, or macOS-specific junk:
- `~/Library/` — macOS only - `~/Library/` — macOS only
@ -113,3 +134,8 @@ These contain caches, build artifacts, or macOS-specific junk:
- [ ] Hyprland: SUPER+D opens rofi, SUPER+Return opens ghostty+zellij - [ ] Hyprland: SUPER+D opens rofi, SUPER+Return opens ghostty+zellij
- [ ] Discord: @mention Marcus → gets a reply - [ ] Discord: @mention Marcus → gets a reply
- [ ] Wallpaper appears after login - [ ] Wallpaper appears after login
- [ ] Firmware updated (`fwupdmgr update`)
- [ ] Fingerprint enrolled (`fprintd-enroll`)
- [ ] S3 sleep working (`cat /sys/power/mem_sleep` shows `[deep]`)
- [ ] Remote Nix builders working (`nix build --rebuild nixpkgs#hello`)
- [ ] Syncthing running for vault sync

View file

@ -131,11 +131,17 @@ laptop-config/
├── flake.nix # Two targets: nixosConfigurations.laptop + homeConfigurations.sscoble ├── flake.nix # Two targets: nixosConfigurations.laptop + homeConfigurations.sscoble
├── modules/ ├── modules/
│ ├── laptop.nix # System packages, zsh, fonts, SSH │ ├── laptop.nix # System packages, zsh, fonts, SSH
│ └── hyprland.nix # Hyprland WM, greetd, PipeWire, portals, bluetooth │ ├── hyprland.nix # Hyprland WM, greetd, PipeWire, portals, bluetooth
│ ├── t490.nix # T490 hardware: TLP, throttled, S3 sleep, remote builders, fwupd
│ └── vm.nix # UTM aarch64 VM for testing
├── home/ ├── home/
│ ├── sscoble.nix # Shared (macOS + NixOS): zsh, git, nvim, pi, dirs, SSH │ ├── sscoble.nix # Shared (macOS + NixOS): zsh, git, nvim, pi, direnv, SSH
│ ├── desktop.nix # Linux-only: Hyprland, waybar, ghostty, zellij, themes │ ├── desktop.nix # Linux-only: Hyprland, waybar, ghostty, zellij, themes
│ └── wgu.nix # WGU software: Zotero, Brave, Obsidian, VSCode, Typst, Bitwarden │ └── wgu.nix # WGU software: Zotero, Brave, Obsidian, VSCode, Typst, Bitwarden
├── docs/
│ ├── T490-HARDWARE.md # WiFi, GPU, audio, fingerprint, sleep/resume guide
│ ├── T490-BATTERY.md # TLP config, threshold strategy, real-world numbers
│ └── T490-OFFLOADING.md # What runs local vs cluster, remote builder setup
└── README.md └── README.md
``` ```
@ -157,6 +163,7 @@ Same zsh, git, neovim, pi config, SSH config on both machines.
|------|--------| |------|--------|
| [sscoble/pi-mono](https://os.scoble.me/forgejo/sscoble/pi-mono) | pi coding agent (mirror of badlogic/pi-mono) | | [sscoble/pi-mono](https://os.scoble.me/forgejo/sscoble/pi-mono) | pi coding agent (mirror of badlogic/pi-mono) |
| [sscoble/wgu](https://os.scoble.me/forgejo/sscoble/wgu) | WGU coursework monorepo — class templates, study strategy, OB1 integration | | [sscoble/wgu](https://os.scoble.me/forgejo/sscoble/wgu) | WGU coursework monorepo — class templates, study strategy, OB1 integration |
| [NixOS/nixos-hardware](https://github.com/NixOS/nixos-hardware) | T490 hardware quirks module (imported via flake input) |
## Forgejo Actions ## Forgejo Actions

56
docs/T490-BATTERY.md Normal file
View file

@ -0,0 +1,56 @@
# T490 Battery Life
## Hardware Baseline
- **50 Wh** internal battery (3-cell)
- Panel draws ~4-5W at typical brightness
- CPU TDP-up 25W, TDP-down 10W
## Real-World Estimates
| Scenario | Runtime |
|---|---|
| Idle, screen off | 10-12 hours |
| Light work (terminal, notes) | 7-8 hours |
| Dev work (editor, browser, kubectl) | 5-6 hours |
| Browsing (10+ tabs, video) | 4-5 hours |
| Compiling / heavy CPU | 1.5-2 hours |
## TLP Configuration
`modules/t490.nix` configures:
- `powersave` governor on battery, `performance` on AC
- Turbo disabled on battery
- CPU capped at 40% max frequency on battery (~1.7 GHz)
- WiFi power saving on battery
- PCIe runtime PM (auto on battery)
- Intel GPU frequency limits (300-600 MHz on battery)
### Battery Health Thresholds
Lenovo Battery Team recommendations — configured in `modules/t490.nix`:
- **Start charging** below 40%
- **Stop charging** at 80%
- Thresholds persist in firmware (survive reboots without TLP)
This dramatically extends battery lifespan. Don't leave plugged in at 100% for days.
## CPU Throttling Fix
Intel Whiskey Lake CPUs have a bug where BD PROCHOT fires spuriously, locking the CPU at 400 MHz. The nixos-hardware T490 module enables `services.throttled` (erpalma/throttled) — this is mandatory.
**Skip thermald** — it conflicts with throttled. `services.thermald.enable = false` is set in `modules/t490.nix`.
## Additional Tweaks
- `pcie_aspm=force` — force PCIe power saving
- `i915.enable_rc6=7` — max GPU power saving
- `noatime,nodiratime` — reduce disk writes
- **zram swap** at 50% of RAM — faster, saves NVMe writes
## Quick Wins (No Config Needed)
- Lower screen brightness (40-60% is plenty — biggest power draw)
- Disable Bluetooth when not in use
- Use S3 deep sleep (not s2idle)
- Close unused browser tabs

63
docs/T490-HARDWARE.md Normal file
View file

@ -0,0 +1,63 @@
# T490 Hardware Compatibility
## WiFi
Two possible cards depending on SKU:
- **Intel Wireless-AC 9560** (CNVio, older) — firmware: `iwlwifi-9000-pu-b0-jf-b0-*.ucode`
- **Intel Wi-Fi 6 AX200** (M.2, discrete, newer) — firmware: `iwlwifi-cc-a0-*.ucode`
Both use `iwlwifi` and work out of the box. If AX200 scan fails after suspend, firmware version may be the culprit. The `modules/t490.nix` includes a resume hook that reloads the module.
## Graphics — Intel UHD 620 (Whiskey Lake)
Single iGPU. No NVIDIA. No Optimus. Ideal for Linux.
| Feature | Support |
|---|---|
| OpenGL 4.6 | Mesa Iris driver |
| Vulkan 1.3 | Mesa ANV driver |
| VA-API decode | H.264, H.265 8-bit, VP9 8+10-bit, MPEG-2, VC-1, VP8 |
| VA-API encode | H.264 only (no H.265) |
| DisplayPort over USB-C | Up to 4096×2304 @ 60Hz |
| HDMI 1.4b | Up to 4096×2304 @ 24Hz |
## Thunderbolt 3
One full-speed TB3 port (also USB-C PD). `services.hardware.bolt.enable = true` in `modules/t490.nix`. Lenovo TB3 Dock Gen 2 works — update dock firmware via fwupd.
## Fingerprint Reader — Synaptics 06cb:00bd
Works after firmware update. Steps:
1. `fwupdmgr refresh && fwupdmgr update` — flashes Prometheus firmware
2. `fprintd-enroll` — enroll fingerprints
3. Works for sudo, login, and screen unlock via PAM
Check: `lsusb | grep 06cb` should show `06cb:00bd`.
## Audio
Single Intel audio card (`8086:9dc8`) with 5 sub-devices (speaker + 4 HDMI/USB-C). The nixos-hardware T490 module provides a custom PulseAudio profile set that exposes all 5 sinks simultaneously.
## Sleep / Resume — Critical
The T490 supports S3 (deep sleep) and S0ix (s2idle, modern standby). Lenovo removed S3 from some BIOS versions.
**Check:**
```
cat /sys/power/mem_sleep
```
| Output | Status | Action |
|---|---|---|
| `s2idle [deep]` | S3 default | Good |
| `[s2idle] deep` | S3 exists but not default | Already fixed in `modules/t490.nix` (`mem_sleep_default=deep`) |
| `[s2idle]` | S3 missing | Troubleshoot below |
**If S3 is missing:**
1. BIOS → Config → Power → Sleep State → "Linux" (re-enables S3 on some versions)
2. Downgrade BIOS (pre-2020 versions have S3)
3. Use hibernation instead (more reliable than s2idle)
**Sleep battery drain:**
- S3 (deep): ~0.5%/hour (~8 days on a 50 Wh battery)
- s2idle: ~2-4%/hour (1-2 days)

49
docs/T490-OFFLOADING.md Normal file
View file

@ -0,0 +1,49 @@
# T490 Offloading Strategy
The k3s cluster (n97, n97-mini) handles CPU-bound work so the T490 stays cool and battery lasts longer.
## Local (T490)
Interactive, latency-sensitive, or IO-bound:
- Firefox (web apps, research)
- Terminal + shell (`ghostty` + `zellij`)
- Editor (`nvim`)
- `kubectl` / `k9s` (just API calls)
- `git` on small repos
- `nixos-rebuild switch` (builds go remote, CLI runs locally)
- SSH client
- Chat (Signal, Discord)
- Obsidian
- Image/document viewing
## Cluster
CPU-bound, batch, or service workloads:
- Heavy Nix derivations during `nixos-rebuild` → remote builders
- Go/Rust/C compilation → ssh/rsync build remotely
- Docker/OCI image builds → build where they run
- Package builds (CI) → Forgejo runners on cluster
- Data processing, ETL
- Kubeclaw agent operations
- Long-running services (Forgejo, registry, web services)
## Remote Nix Builders
Configured in `modules/t490.nix`:
```nix
nix.distributedBuilds = true;
nix.buildMachines = [
{ hostName = "n97-mini"; system = "x86_64-linux"; maxJobs = 4; ... }
{ hostName = "n97"; system = "x86_64-linux"; maxJobs = 4; ... }
];
```
**Setup:** `ssh-keygen -t ed25519 -f /root/.ssh/remote-build` on T490, copy pubkey to each builder.
**When it works:** `nixos-rebuild switch --flake .#laptop` ships heavy derivations to the cluster. The T490 only builds what must be local (bootloader, activation scripts).
## Dev Compilation Patterns
- **sshfs mount:** Edit locally, files live on cluster → build via SSH
- **rsync + remote build:** Push source, build, pull binary back
- **nix develop with builders:** `nix develop .#project --builders "ssh://n97-mini"`

View file

@ -11,9 +11,10 @@
url = "git+https://os.scoble.me/forgejo/sscoble/pi-mono.git"; url = "git+https://os.scoble.me/forgejo/sscoble/pi-mono.git";
flake = false; flake = false;
}; };
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
}; };
outputs = { self, nixpkgs, home-manager, pi-mono, ... }@inputs: let outputs = { self, nixpkgs, home-manager, pi-mono, nixos-hardware, ... }@inputs: let
linuxSystem = "x86_64-linux"; linuxSystem = "x86_64-linux";
darwinSystem = "aarch64-darwin"; darwinSystem = "aarch64-darwin";
@ -66,6 +67,8 @@
home-manager.nixosModules.home-manager home-manager.nixosModules.home-manager
./modules/laptop.nix ./modules/laptop.nix
./modules/hyprland.nix ./modules/hyprland.nix
./modules/t490.nix
nixos-hardware.nixosModules.lenovo-thinkpad-t490
{ nixpkgs.config.allowUnfree = true; } { nixpkgs.config.allowUnfree = true; }
{ {
home-manager = { home-manager = {

157
modules/t490.nix Normal file
View file

@ -0,0 +1,157 @@
{ config, pkgs, lib, ... }:
{
# === T490-specific hardware configuration ===
# Requires nixos-hardware flake input: nixos-hardware.nixosModules.lenovo-thinkpad-t490
# The nixos-hardware module provides:
# - services.throttled (CPU throttling fix)
# - PulseAudio profile set (5 simultaneous sinks)
# - common/cpu/intel (VA-API drivers, microcode)
# === Kernel parameters ===
boot.kernelParams = [
"mem_sleep_default=deep" # prefer S3 deep sleep over s2idle
"pcie_aspm=force" # PCIe Active State Power Management
"i915.enable_fbc=1" # frame buffer compression (power saving)
"i915.enable_psr=1" # panel self-refresh (power saving)
"i915.enable_rc6=7" # maximum GPU render power saving
];
boot.kernelModules = [ "thinkpad_acpi" ];
boot.blacklistedKernelModules = [
"iTCO_wdt"
"iTCO_vendor_support"
];
# === Display DPI ===
services.xserver.dpi = 157; # 14" 1920×1080 IPS = 157 DPI
# === Firmware updates ===
services.fwupd.enable = true;
# === Fingerprint reader (Synaptics 06cb:00bd) ===
# Requires firmware update via fwupd first
services.fprintd.enable = true;
# === TLP — ThinkPad power management ===
# nixos-hardware thinkpad module provides base config; tune here
services.tlp = {
enable = true;
settings = {
CPU_SCALING_GOVERNOR_ON_AC = "performance";
CPU_SCALING_GOVERNOR_ON_BAT = "powersave";
CPU_ENERGY_PERF_POLICY_ON_AC = "balance_performance";
CPU_ENERGY_PERF_POLICY_ON_BAT = "power";
CPU_MAX_PERF_ON_AC = 100;
CPU_MAX_PERF_ON_BAT = 40; # cap at ~1.7 GHz on battery
CPU_BOOST_ON_AC = 1;
CPU_BOOST_ON_BAT = 0; # disable turbo on battery
START_CHARGE_THRESH_BAT0 = 40; # battery health: start charging below 40%
STOP_CHARGE_THRESH_BAT0 = 80; # battery health: stop at 80%
WIFI_PWR_ON_AC = "off";
WIFI_PWR_ON_BAT = "on";
RUNTIME_PM_ON_AC = "on";
RUNTIME_PM_ON_BAT = "auto";
USB_AUTOSUSPEND = 1;
INTEL_GPU_MIN_FREQ_ON_AC = 300;
INTEL_GPU_MAX_FREQ_ON_AC = 1100;
INTEL_GPU_MIN_FREQ_ON_BAT = 300;
INTEL_GPU_MAX_FREQ_ON_BAT = 600;
};
};
# === powertop ===
powerManagement.powertop.enable = true;
# Skip thermald — conflicts with throttled (enabled by nixos-hardware)
services.thermald.enable = false;
# === zram swap (faster + saves NVMe writes) ===
zramSwap = {
enable = true;
memoryPercent = 50;
};
# === Intel GPU — hardware video acceleration ===
hardware.graphics = {
enable = true;
extraPackages = with pkgs; [
intel-media-driver # modern VA-API (iHD)
intel-vaapi-driver # legacy VA-API (i965)
libvdpau-va-gl # VDPAU → VA-API bridge
];
};
environment.sessionVariables = {
LIBVA_DRIVER_NAME = "iHD";
MOZ_ENABLE_WAYLAND = "1";
};
# === Suspend/resume: reload WiFi after wake ===
powerManagement.powerUpCommands = ''
${pkgs.kmod}/bin/modprobe -r iwlmvm iwlwifi || true
${pkgs.kmod}/bin/modprobe iwlwifi || true
'';
# === Remote Nix builders (k3s cluster) ===
nix.distributedBuilds = true;
nix.buildMachines = [
{
hostName = "n97-mini";
system = "x86_64-linux";
maxJobs = 4;
supportedFeatures = [
"nixos-test"
"benchmark"
"big-parallel"
"kvm"
];
sshUser = "root";
sshKey = "/root/.ssh/remote-build";
}
{
hostName = "n97";
system = "x86_64-linux";
maxJobs = 4;
supportedFeatures = [
"nixos-test"
"benchmark"
"big-parallel"
"kvm"
];
sshUser = "root";
sshKey = "/root/.ssh/remote-build";
}
];
# === Syncthing (Obsidian vault sync) ===
services.syncthing = {
enable = true;
user = "sscoble";
dataDir = "/home/sscoble/.syncthing";
configDir = "/home/sscoble/.syncthing/config";
};
# === Tailscale (consistent cluster access from any network) ===
services.tailscale.enable = true;
# === NVMe tools ===
environment.systemPackages = with pkgs; [ nvme-cli ];
# === Touchpad & TrackPoint ===
services.xserver.libinput = {
enable = true;
touchpad = {
tapping = true;
naturalScrolling = true;
accelSpeed = "0.3";
disableWhileTyping = true;
};
};
# TrackPoint sensitivity
services.udev.extraRules = ''
ACTION=="add", SUBSYSTEM=="input", ATTR{name}=="*TrackPoint*", \
ATTR{device/sensitivity}="200", ATTR{device/speed}="150"
'';
}