commit c4613af53be2c0c66ccd80b5d4efa06a4bd1306d
parent 9405cde151ec86950d60f3c81eef403556659d1d
Author: DAEMON-404 <zer0sec.xp@icloud.com>
Date: Wed, 7 Oct 2026 22:32:50 +0100
Add fan-extras (performance profile oneshot) and README
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
4 files changed, 169 insertions(+), 0 deletions(-)
diff --git a/README.md b/README.md
@@ -0,0 +1,144 @@
+# NixDaemon
+
+NixOS + home-manager for the PCSpecialist Valeon II 17 (TongFang GM7RGxM):
+Hyprland (Lua config, uwsm) with Caelestia Shell, the dead-GPU-fan workaround,
+and the hand-written toolbox. Built from the vault's
+`04Tools/NixDaemon-Migration/` material on 2026-10-07.
+
+## Layout
+
+```
+flake.nix inputs: nixpkgs (unstable), nixpkgs-stable (26.05, bootstrap only),
+ home-manager, hyprland, caelestia-shell, caelestia-cli, llm-agents
+hosts/laptop/ the machine
+ default.nix boot, users, greetd/tuigreet, Hyprland (uwsm), audio, fonts, portals
+ fan-throttle-guard.nix vault gpu-fan-fix/, imported unchanged; fanfix + stability_guard.py beside it
+ fan-extras.nix the performance power profile (fanfix install did this by hand on Arch)
+ uniwill-laptop.nix the `uniwill` hwmon the guard reads, built from Linux 6.19 sources (see below)
+ nvidia.nix open kernel module, panel on the dGPU, device order for Hyprland
+ ssd.nix Samsung 980 crypttab + /mnt/ssd (key restored by hand, see below)
+ nix-settings.nix flakes, hyprland.cachix.org
+ toolbox.nix envfs (foreign shebangs), ~/.local/bin first on PATH, padx udev rule
+ fan-reference/ the Arch-era captures (fan-ctl, fan-state, units, confs) and their README
+hosts/bootstrap/ stage A: today's GNOME install + fan fix + toolbox prerequisites
+home/ home-manager for daemonsec
+ modules/hyprland.nix Lua config wiring, helper scripts, polkit agent, cliphist, udiskie
+ modules/caelestia.nix programs.caelestia, the carried shell.json, static Rosé Pine scheme
+ modules/terminal.nix kitty, DMMono Nerd Font, Rosé Pine colours
+ modules/tools.nix toolbox runtime closure, python env, dotfiles symlinks (fonts, cursors)
+ modules/gtk.nix Yaru-purple, cursor, prefer-dark
+ hypr/*.lua omarchy (shim) · core · defaults (stock Omarchy binds) · bindings · lid · caelestia
+ caelestia/ scheme.json, rose-pine-dark.txt (CLI scheme), shell-tokens.json
+modules/workstation.nix Claude Code, Claude desktop, Obsidian, gh, glab (from the bootstrap script)
+```
+
+## Applying
+
+Everything below needs `sudo`, so it is left to the owner.
+
+**Stage A: fan fix now, on the GNOME install.** Small switch (fan module,
+driver, toolbox prerequisites, Hyprland cache). The new kernel modules only
+load from the booted system, hence the reboot.
+
+```sh
+sudo nixos-rebuild switch --flake ~/NixDaemon#bootstrap && sudo reboot
+```
+
+First-boot check (vault README and gpu-fan-fix/README.md):
+
+```sh
+fanfix status # cap 3200 MHz · boost 1 · profile performance · fan line present
+sudo fanfix fan status # fan-abnormal=1 is expected; universal-fan-ctrl / custom-tables show the live EC path
+fanfix test 30 # all-core stress: expect 0 throttle events, peak < 75 °C
+systemctl status motherboard-stability fanfix-fan fanfix-performance-profile
+cat /run/motherboard-stability/status.json # limit_mhz 3200, thermal_stage 0, board_gpu_c and main_fan_rpm present
+```
+
+`fanfix status` will say "cap is not persisted": on NixOS the floor is the
+`systemd.tmpfiles.rules` line in the module, not `/etc/tmpfiles.d/99-cpu-freq-cap.conf`.
+Treat `fanfix install` / `uninstall` / `fan setup` as no-ops here; change
+`capKhz` in the module instead (gpu-fan-fix README).
+
+**Stage B: the desktop.** Build, then boot into it (everything heavy is already
+in the store if the build below finished; otherwise this downloads Hyprland
+from its cache and compiles the NVIDIA modules).
+
+```sh
+sudo nixos-rebuild boot --flake ~/NixDaemon#nixos && sudo reboot
+```
+
+tuigreet appears on tty1; pick `Hyprland (uwsm)` once, it is remembered.
+Then the second vault check, the keybind diff:
+
+```sh
+hyprctl binds -j | python3 -I -c 'import json,sys
+M={1:"SHIFT",4:"CTRL",8:"ALT",64:"SUPER"}
+for x in json.load(sys.stdin):
+ print(x.get("submap",""),"|","+".join(n for v,n in sorted(M.items()) if x["modmask"]&v),"|",x["key"],"|",x.get("description",""))' | sort > /tmp/binds.new
+cut -d'|' -f1-4 ~/git/NetrunnerVault/04Tools/NixDaemon-Migration/shortcuts/keybinds.txt | sort | diff - /tmp/binds.new
+```
+
+Expected differences: the keycode binds (workspaces, resize, bar panels,
+group windows) show `code:0` in the capture and an empty key here; the keys
+caelestia.lua takes over carry their Caelestia descriptions (launcher,
+session menu, panels, notifications, media keys); the stock Obsidian and
+YouTube lines are gone because vault-open and bakx own those keys; the two
+webcam-overlay binds were not carried (keycodes unknown).
+
+Afterwards delete `hosts/bootstrap/` and the `nixpkgs-stable` input.
+
+**Samsung SSD key** (vault samsung-ssd.md, section 2; needs the gpg passphrase):
+
+```sh
+cd ~/git/NetrunnerVault/04Tools/NixDaemon-Migration
+sudo mkdir -p -m 700 /etc/secrets
+gpg -d --pinentry-mode loopback ssd.key.gpg | sudo tee /etc/secrets/ssd.key >/dev/null
+sudo chmod 400 /etc/secrets/ssd.key
+sudo systemctl restart systemd-cryptsetup@ssd.service mnt-ssd.mount # or just reboot
+```
+
+Until then the drive stays locked; both units are `nofail`, so boot is unaffected.
+
+## The toolbox
+
+`~/.local/bin` is the vault's `bin/` copied flat and `git init`ed (remote
+`gitlab` → `DAEMON-404/daemon-bin`, not pushed). NixOS puts it first on PATH
+and envfs resolves the `#!/bin/bash` and `#!/usr/bin/python3` shebangs.
+`install.sh` there reports the environment. Notes:
+
+- `dropterm`, `winsnap`, `vault-open`, `lid-control` call
+ `hyprctl dispatch 'hl.dsp…'`: that is Hyprland 0.56's Lua dispatch syntax,
+ so they work unchanged.
+- `winsnap` looks for an `omarchy-bar` layer to avoid the bar; Caelestia's
+ layers are `caelestia-*`, so snaps ignore the bar's reserved edge for now.
+- `lid-control` still calls a few `omarchy-*` helpers (tolerated: they fail
+ quietly); `clipboard-backup` and `omarchy-menu-tmux-keybindings` are bound
+ but not in the carried `bin/`.
+- The cheat popups (`omarchy-menu-kitty`, …) pipe into `omarchy-menu-select`,
+ provided here as a fuzzel wrapper (home/modules/hyprland.nix).
+
+## Why `uniwill-laptop` is built here
+
+`stability_guard.py` reads the `uniwill` hwmon (board GPU temperature, main
+fan rpm) and falls back to a permanent 1.8 GHz ceiling without it. The driver
+was merged upstream in Linux 6.19; nixpkgs' 6.18 kernel predates it and the
+7.2 kernel config leaves its Kconfig submenu off. `hosts/laptop/uniwill-laptop/`
+holds the v6.19 sources and builds them as an out-of-tree module against
+whatever kernel is selected (verified on 6.18.55). Revisit when the default
+NixOS kernel ships it.
+
+## Parked for the owner
+
+- **ANSI green**: Rosé Pine puts pine (#31748f) in the green slot; the rule
+ says pine is never ink. kitty uses foam (#9ccfd8) for color2/color10
+ meanwhile; one variable in home/modules/terminal.nix.
+- **Display manager**: greetd + tuigreet chosen (text greeter, remembers
+ user and session). sddm would be a one-file swap.
+- **GPU / MUX**: configured for what the firmware presents, the panel on the
+ RTX 3070 Ti (discrete). The hybrid alternative is a commented block in
+ nvidia.nix; it only applies after changing the MUX in the BIOS.
+- **Hostname** stays `nixos` (the installer's). The flake output is also `nixos`.
+- Programs some stock Omarchy keys expect but which are not in the toolbox
+ closure (spotify, lazydocker, btop, tmux, yazi, neovim, 1password,
+ signal): those keys show a notification saying so. Add packages to
+ home/modules/tools.nix when wanted.
diff --git a/hosts/bootstrap/default.nix b/hosts/bootstrap/default.nix
@@ -17,6 +17,7 @@
imports = [
../laptop/hardware-configuration.nix
../laptop/fan-throttle-guard.nix
+ ../laptop/fan-extras.nix
../laptop/uniwill-laptop.nix
../laptop/nix-settings.nix
../laptop/toolbox.nix
diff --git a/hosts/laptop/default.nix b/hosts/laptop/default.nix
@@ -8,6 +8,7 @@
imports = [
./hardware-configuration.nix
./fan-throttle-guard.nix # dead-GPU-fan workaround (vault gpu-fan-fix/, imported unchanged)
+ ./fan-extras.nix # the one imperative step fanfix install did: the performance profile
./uniwill-laptop.nix # the `uniwill` hwmon the guard reads: kernel 6.19 driver built for this kernel
./nvidia.nix
./ssd.nix
diff --git a/hosts/laptop/fan-extras.nix b/hosts/laptop/fan-extras.nix
@@ -0,0 +1,23 @@
+# hosts/laptop/fan-extras.nix
+#
+# The part of the fan fix that `fanfix install` did imperatively on Arch and
+# that fan-throttle-guard.nix (imported unchanged) does not carry: the
+# "performance" power profile. power-profiles-daemon persists the choice, so
+# this oneshot is idempotent; it runs after ppd is up and then re-applies the
+# tmpfiles clock floor, because a profile switch rewrites per-policy boost and
+# can lift scaling_max_freq (fanfix re-runs tmpfiles for the same reason; the
+# stability guard would also catch it within a second).
+{ pkgs, lib, ... }:
+{
+ systemd.services.fanfix-performance-profile = {
+ description = "fanfix: select the performance power profile and re-assert the clock floor";
+ after = [ "power-profiles-daemon.service" "systemd-tmpfiles-setup.service" ];
+ requires = [ "power-profiles-daemon.service" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.power-profiles-daemon}/bin/powerprofilesctl set performance";
+ ExecStartPost = "${pkgs.systemd}/bin/systemd-tmpfiles --create --prefix=/sys/devices/system/cpu";
+ };
+ };
+}