Commit Graph

651 Commits

Author SHA1 Message Date
Bernardo Magri
7d6d74fd7f test(power): VM check for battery charge-limit udev re-apply
Add checks.battery-charge-limit (runNixOSTest): the test_power module
fakes a Mains adapter, toggling ac_online emits a real power_supply
uevent, and the test asserts the udev rule restarts the oneshot (changed
InvocationID). Verifies the re-apply *trigger* at runtime, beyond the
earlier eval check; the sysfs write still needs real hardware. Also
records the SSH_AUTH_SOCK rendered-artifact verification in the roadmap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:41:24 +01:00
Bernardo Magri
cdd1897b14 feat(keys): session-level SSH_AUTH_SOCK for GUI clients
The agent's SSH socket was only exported via the zsh integration, so a
GUI client launched outside a shell (rofi launcher, autostarted apps)
couldn't use gpg-agent for SSH. Add a home.sessionVariables export
resolving the socket with gpgconf --list-dirs agent-ssh-socket — the
same lookup the shell integration uses, so the two can't drift — which
reaches GUI apps via the login shell that starts Hyprland (the path
NIXOS_OZONE_WL already takes).

Eval-verified: the var resolves to the gpgconf substitution; the home
config still builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:27:07 +01:00
Bernardo Magri
9976ea06f5 feat(power): re-apply battery charge limit on AC state change
The threshold was written boot-only, so a firmware that clears
charge_control_end_threshold on unplug would silently lose the cap
until the next reboot. Add a services.udev rule on the mains adapter
(ATTR{type}=="Mains", vendor-neutral) that restarts the oneshot via
systemctl --no-block on every AC state change. Uses restart (not
try-restart) so it re-applies even if the boot run was inactive.

Eval-verified: rule present when the charge limit is on, absent when
off; downstream-template-system still evaluates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:10:44 +01:00
Bernardo Magri
cdfe92a089 feat(hardware): hide dual-sensor webcam IR node (fixes dark image)
A built-in RGB+IR webcam (common on recent ThinkPads, e.g. the T14s AMD
Gen 4) exposes its IR face-unlock sensor as a second, identically-named
"Integrated Camera"; apps that pick it show a dark, 8-bit-greyscale
image. nomarchy.hardware.camera.hideIrSensor disables that node on the
V4L2 PipeWire path (WirePlumber monitor.v4l2.rules) so only the colour
camera is offered, matched by card name (overridable irMatch).

- libcamera is left untouched: an external camera you plug in is never
  affected, and surgical internal-only libcamera scoping isn't possible
  (its distinguishing props bind after the monitor rule runs).
- Only the PipeWire node is hidden; the kernel /dev/video* stays open,
  so Howdy face-unlock still reads the IR sensor directly.
- The installer's hardware-db.sh auto-detects a paired RGB+IR webcam
  from /sys/class/video4linux/*/name and bakes the toggle into
  system.nix; commented example added to the downstream template.

Validated live on a T14s: collapses the four enumerated "Integrated
Camera" entries to one colour source; the exact shipped regex disables
the IR node (confirmed in the WirePlumber log) and leaves libcamera
intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:39:49 +01:00
Bernardo Magri
431af618cc feat(tailscale): operator-set login user — VPN menu skips the sudo terminal
nomarchy.services.tailscale now makes the login user the Tailscale
operator (services.tailscale.extraSetFlags = ["--operator=<username>"],
mkDefault), so `tailscale up/down/set` — and the System → VPN menu's
Tailscale controls — run without sudo. The user is already in wheel, so
the operator grant is no real new privilege, just skips the prompt.

nomarchy-vpn's Tailscale actions are now operator-aware: a ts_priv
helper runs the subcommand inline and only falls back to a sudo terminal
if the operator grant is absent (downstream dropped it). Connect brings
an authed-but-down node up inline; a node that still needs interactive
login goes to a terminal so the auth URL is visible.

Option description + template comment updated; ROADMAP follow-up closed.
System + home builds green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:26:15 +01:00
Bernardo Magri
47526ae6e2 feat(menu): VPN setup & management submenu (WireGuard/OpenVPN/Tailscale)
A dedicated System → VPN flow (nomarchy-vpn) past the wifi picker:
- NetworkManager VPN/WireGuard connections shown ● active / ○ inactive,
  toggled up/down on select (networkmanager-group users need no sudo).
- Import a *.conf / *.ovpn via the fd picker → nmcli connection import,
  type chosen by extension. networkmanager-openvpn ships system-side
  (networking.networkmanager.plugins, mkDefault) so the openvpn type is
  available; WireGuard is native to NetworkManager.
- Tailscale block, self-gated on the CLI (= nomarchy.services.tailscale):
  read-only status + up/down + exit-node; privileged actions go through a
  sudo terminal (the snapshot pattern), no operator wiring.

Waybar custom/vpn shield (nomarchy-vpn-status): self-hides unless a NM
tunnel or Tailscale is up; @good tone; click opens the submenu. Wired
into the generated bar and both summer whole-swaps. Decided import-first;
creation deferred to nm-connection-editor. ROADMAP marked shipped.

Eval + home/system builds green; both scripts pass bash -n. Pending an
on-machine check (live nmcli import/connect + Tailscale paths).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:13:03 +01:00
Bernardo Magri
86802f244e docs(roadmap): system VPN setup & management menu (WireGuard/OpenVPN/Tailscale)
A forward-looking item for a dedicated System → VPN flow: import
WireGuard .conf / OpenVPN .ovpn into NetworkManager and toggle them,
plus Tailscale up/down + exit-node (self-gated on nomarchy.services.
tailscale). Notes the plugin dep (networkmanager-openvpn), the Files
picker + Waybar-parity patterns, secrets staying in the connection
manager, and the import-first-vs-creation open question.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:35:11 +01:00
Bernardo Magri
f3325385c1 feat(timezone): opt-in automatic, location-following timezone
nomarchy.system.autoTimezone (off by default): geoclue + automatic-
timezoned drive /etc/localtime from your location, so travelling to
another zone updates the Waybar clock on its own.

Menu-driven, in-flake state (the night-light / keyboard philosophy):
the flag is settings.autoTimezone in theme-state.json (git-tracked); a
System-menu entry toggles it. Because it's a SYSTEM service — not a user
unit it can start/stop instantly like night-light — the toggle
(nomarchy-autotimezone, run in a terminal) writes the flag --no-switch
then drives `sudo nixos-rebuild` (bakes the service + the time.timeZone
override) plus `home-manager switch` (the Waybar-refresh watcher); both
sides read the one flag.

- modules/nixos/timezone.nix: parses settings.autoTimezone from
  nomarchy.system.stateFile (like Plymouth), gates geoclue2 +
  automatic-timezoned, and mkForce-nulls time.timeZone — automatic-
  timezoned sets it null at normal priority, which collides with the
  installer's static value (a hard eval error), so force wins and
  reverts cleanly when disabled. Ships the nomarchy-autotimezone toggle.
- modules/home/timezone.nix: a tiny user service watching timedate1's
  change signal that reloads Waybar (SIGUSR2) on a real zone change, so
  the clock follows live (Waybar captures the zone at construction).
  Gated on the same flag via nomarchy.settings.autoTimezone.
- rofi menu: "Auto timezone (on/off)" in the System submenu.
- option + template example + ROADMAP entry; theme.nix seeds the flag.

Eval-verified on (geoclue+daemon on, tz forced null over a static
installer value, watcher present) and off (static tz intact, nothing
extra); home + system both build green. Needs an on-hardware check
(geoclue detection + the SIGUSR2 clock refresh aren't CI-testable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:34:45 +01:00
Bernardo Magri
aed41793f8 feat(keyboard): per-device layout memory in-flake (no ~/.local/state)
Phase 2 of the in-flake-state philosophy, mirroring night-light: the
nomarchy-keyboard-watch daemon now remembers per-device layouts in the
git-tracked state file (settings.keyboard.devices) instead of
~/.local/state/nomarchy/keyboard-layouts.

- Reads the LIVE working-tree map (theme-sync get → jq) so a pick this
  session is honoured at once; writes instantly with --no-switch (no
  rebuild). Writes the whole map back at the dot-free parent path
  (read-modify-write) so a device name with a dot can't corrupt the
  tool's dotted set-path (Hyprland only sanitises spaces→-).
- Graduation: nomarchy.keyboard.devices now mkDefault-merges entries
  from settings.keyboard.devices, so each remembered device becomes a
  generated Hyprland device{} block on the next rebuild (reproducible,
  hotplug-applied). A hand-written keyboard.devices.<name> still wins;
  once baked, the watcher's is_declared check makes it step back.
- theme.nix seeds settings.keyboard.devices = {} (sparse state still
  evaluates); options/template descriptions updated; ROADMAP marked done.

Eval-verified (graduation + mkDefault precedence) and the set/get
writer round-trips. Still needs the on-hardware hotplug re-verify (the
picker path isn't testable in CI).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:18:33 +01:00
Bernardo Magri
685126ab47 feat(nightlight): menu-driven, in-flake on/off (no ~/.local/state)
Night light is off by default and configured entirely through the menu,
writing into the downstream flake instead of ~/.local/state — so the
choice is git-tracked and reproducible across a clone+rebuild.

Two flags under a new settings.* section of the state file (exposed as
the nomarchy.settings option):
- settings.nightlight.installed — sticky, gates the hyprsunset unit;
  nomarchy.nightlight.enable mkDefault-reads it. First enable from the
  menu rebuilds to create the unit (the one accepted rebuild).
- settings.nightlight.on — runtime on/off, toggled instantly
  (theme-sync set --no-switch + systemctl), no rebuild. An ExecCondition
  reads the live flag at session start so an off survives reboot;
  splitting it from the sticky flag means a later unrelated rebuild
  never undoes an instant-off (no decay).

Drops the ~/.local/state/nomarchy/nightlight-off marker and the
ConditionPathExists hack. theme-sync set now skips the wallpaper
re-apply for non-wallpaper keys, and its rebuild notifications are
generic ("changes" not "theme").

First cut of the broader principle — any user-settable config gets a
menu writer that lands it in the flake; no state outside the checkout.
Docs (README §4, ROADMAP, template) updated to reflect it.

Eval- and round-trip-verified; on-hardware check pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 20:24:58 +01:00
Bernardo Magri
c8d0b09044 docs(readme): label which file each nomarchy.* option goes in
Split the options reference into separate home.nix / system.nix tables
and note the prefix rule (system/hardware/services → system.nix, the
rest → home.nix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 16:42:03 +01:00
Bernardo Magri
c2f90c7d0a feat(menu): printer setup — system-config-printer in the System submenu
A `nomarchy-menu printers` entry opens system-config-printer (the CUPS
admin GUI), mirroring the bluetooth/blueman pattern: the printing service
ships the package via environment.systemPackages and the menu execs it,
self-gated on the system-config-printer binary so the entry appears only
when nomarchy.services.printing is on. No direct keybind (rare one-off).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:09:35 +01:00
Bernardo Magri
5747dc9839 feat(nightlight): persistent off via state marker
A manual `off` now drops ~/.local/state/nomarchy/nightlight-off and the
hyprsunset unit refuses to (re)start while it exists
(Unit.ConditionPathExists=!%h/...), so the off survives logout/reboot
until toggled back on. `on`/toggle-on remove the marker before starting
(a failed condition would otherwise no-op the start). Without the marker
behaviour is unchanged: the service stays WantedBy=graphical-session.target
and resumes its schedule at login, so persistence is opt-in via the toggle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:09:22 +01:00
Bernardo Magri
d1344712b8 docs(roadmap): note automated upstream lock-bump CI
Capture the maintainer-side plan to automate advancing Nomarchy's own
flake.lock (the single-input delivery channel): a bot drives `main`
behind `nix flake check` + the runNixOSTest suite, and a human still
promotes `main -> v1` behind the on-hardware QA gate that can't be CI'd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:45:56 +01:00
Bernardo Magri
37204f5f45 feat(themes): per-theme rofi identities for nord/retro-82/lumon/kanagawa
Author a designed rofi.rasi whole-swap for the four remaining identity
themes, so all six now carry a distinct menu look (summer-day/night
already shipped their legacy ports). Each is built from the theme's
character rather than a legacy layout (their waybar overrides are
palette-only, so there was nothing to port):

- nord:     soft rounded "frost panel" — frost border, brighter-frost
            selection, aurora-purple prompt
- retro-82: sharp CRT terminal — square corners, amber-on-navy, a teal
            scanline underline, mono
- lumon:    clinical cyan "bezel" — thick cyan frame, a framed-not-filled
            readout inputbar, mono
- kanagawa: ink & paper — warm washi-paper frame, deep ink-well inputbar,
            crystal-blue wave selection

Each file is self-contained (it replaces the generated theme) and keeps
the element structure the theme-grid picker's per-invocation -theme-str
layers onto. rofi.nix needs no change — its existing themes/<slug>/
rofi.rasi probe whole-swaps when the file is present. All four parse
clean under `rofi -dump-theme`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:45:37 +01:00
97b5944dc1 fix(menu): force-own networkmanager-dmenu config.ini
networkmanager_dmenu writes a default config on first run, so an
unmanaged file can already sit at ~/.config/networkmanager-dmenu/
config.ini and abort `home-manager switch` with a clobber error. We
fully generate this file, so set force = true to overwrite it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:27:35 +01:00
0c483f9512 feat(menu): network + audio modules; case-insensitive fuzzy search
- network: replace the nmtui-in-a-terminal flow with networkmanager_dmenu
  (native rofi wifi/VPN picker), configured via xdg config.ini to drive
  rofi with rofi_highlight; editing a connection drops to nmtui.
- audio: new System -> Audio sink/source switcher via rofi-pulse-select,
  self-gated on the PipeWire/pulse socket.
- search: case-insensitive fuzzy matching with fzf sorting across every
  menu module and the launcher (so "system" finds "System").

Secrets module (rbw/pass) deferred — no Bitwarden/pass setup here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:21 +01:00
dfb57c2e34 feat(home): create XDG user directories by default
Enable xdg.userDirs with createDirectories so a fresh install lands with
Downloads/Documents/Pictures/Music/Videos/Desktop/Public/Templates (plus
home-manager's Projects) present and written to user-dirs.dirs, rather
than appearing only on first app use. mkDefault so a downstream home.nix
can flip it off or remap individual paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:21 +01:00
995810927d fix(install): pin HOME=/root so root nix never strays into /home/nomarchy
`exec sudo --preserve-env` (needed to carry the NOMARCHY_* vars) also
drags the live session user's HOME=/home/nomarchy into the root install
process. Root-run `nix build` then writes an eval cache + .nix-defexpr
there, and the in-chroot one lands on the TARGET as a stray, orphaned
/home/nomarchy (no such user on the installed system). Pin root's own
HOME after the sudo re-exec, and again inside the nixos-enter chroot
script. The user activation already sets HOME=/home/$USERNAME and is
unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:21 +01:00
e1cf190dd2 feat(menu): visual theme picker — rofi preview grid
Replace the plain-text `nomarchy-menu theme` list with a grid of real
desktop previews. The grid, the Name→slug map and the active `✓` mark
are generated at eval time from the preset JSONs (`readDir` + `fromJSON`
in rofi.nix); "active" is just `t.slug`, since every switch rebuilds the
menu. Grouped dark-first then light in one scrollable grid (the previews
make the mode obvious — no light/dark submenu split), `flow: horizontal`
so Down scrolls row-by-row.

Previews are committed as themes/<slug>/preview.png at 480×270 (~2.4 MB
for all 21). rofi's element-icon `size` is single-value → a *square*
cell that *contains* the icon, so a 16:9 preview would letterbox; a
build-time imagemagick step centre-crops each to a square so it fills
the cell edge-to-edge (source stays 16:9, crop is reversible). The
window width is derived from `themeGridIconW` so a column is exactly the
icon side — no slack margins. Themes without a preview degrade to a
plain-name row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 17:26:39 +01:00
Bernardo Magri
70334e68bb docs(roadmap): record the faster-switches diagnosis + decided approach
Flesh out the "Faster switches" item with the confirmed diagnosis (themes/
is 86 MB and backgrounds/ is all of it; the config is ~208 KB; wallpapers
are never read at eval; theme-state.json is git-tracked so every apply
re-copies the whole 86 MB source) and the decided-but-deferred approach:
Option 1, a separate pinned wallpapers artifact (repo/tarball via flake
input/fetchurl, read through the existing NOMARCHY_DEFAULT_THEMES hook),
which keeps eval pure. Notes the rejected impure-state alternative and the
pre-built-theme-variants follow-on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:13:08 +01:00
Bernardo Magri
6a4af69b0f feat(updates): passive update-awareness indicator + notification
Add opt-in nomarchy.updates (modules/home/updates.nix): a nomarchy-updates
checker on a systemd user timer (interval, default daily) that surfaces a
Waybar indicator + a notification when updates are available, without ever
changing anything (you still run sys-update / flatpak update).

It counts:
- flake inputs behind upstream — only the flake's DIRECT inputs (root.inputs:
  nixpkgs, the Nomarchy input, home-manager, stylix…), not the transitive
  closure, via `git ls-remote` vs the locked rev. Offline → skipped, never a
  false alarm.
- Flatpak updates, when the flatpak CLI is present (.flatpak, gated on
  services.flatpak being on) — Bernardo's suggestion.

Waybar custom/updates self-gates (hidden until the timer finds something;
accent "󰚰 N", signal 9 for instant refresh), in the generated bar and both
summer whole-swaps. The notification fires only when the count grows, so a
daily timer doesn't nag. Click → the upgrade flow in a terminal (sys-update /
flatpak update, each confirmed). nomarchy-updates ships always on PATH and
self-gates so the static whole-swap bars can exec it even when the feature
is off.

Verified: flake check clean; home generation builds; the jq direct-input
filter + status self-gate exercised against the real flake.lock; script
passes bash -n. Pending an on-machine check (ls-remote/notify/timer need a
live session).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:59:09 +01:00
Bernardo Magri
d9466d6555 docs(roadmap): plan the visual theme picker (preview thumbnails)
Add the agreed approach for prettifying nomarchy-menu theme into a rofi icon
grid of real themed-desktop previews: Bernardo captures previews on real
hardware and commits themes/<slug>/preview.png; a build-time derivation
downscales each to a thumbnail that the picker loads as the rofi element icon
(rofi already renders image files — same as the Papirus menu icons), with a
wallpaper/swatch/plain-name fallback while previews are added incrementally.

Notes the headless VM-render route was prototyped and works (runNixOSTest +
software-GL Hyprland + machine.screenshot QMP dump) but real-hardware capture
was chosen for fidelity; the working VM recipe is parked in memory. The
temporary flake checks.theme-shot spike is removed (too heavy for a CI gate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:36:01 +01:00
Bernardo Magri
0e42763aea feat(menu): Back on every submenu + working snapshot fallback
Two menu improvements.

Back everywhere: every list menu now ends with a "↩ Back" entry that
returns one level up — power, theme, power-profile, clipboard, files,
capture, not just Tools/System — so you no longer have to Esc out and
reopen to back up. A `back` helper + a shared `BACK` label keep it uniform;
matched exactly so it can't collide with clipboard/filename content. Esc
still quits instantly.

Snapshot fallback: btrfs-assistant 2.2 segfaults on 26.05 (libbtrfsutil
ABI), so `nomarchy-menu snapshot` now launches nomarchy-snapshots instead —
a keyboard-driven snapper browser/restore. snapper is root-only here and
rofi can't run as root under Wayland, so it runs in a terminal via sudo
(one password prompt) and uses fzf to pick: browse/diff (read-only),
restore files (undochange), or roll back (root config only), each behind a
typed-`yes` confirmation — safer than a one-click rofi rollback.
btrfs-assistant stays installed for when nixpkgs fixes it.

Verified: flake check clean; home generation builds and the menu passes
bash -n with all Back + snapshot wiring; nomarchy-snapshots passes
shellcheck (writeShellApplication) in a snapper-enabled system build.
Pending an on-machine check of the restore/rollback paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:54:42 +01:00
Bernardo Magri
a6d6860054 docs(roadmap): note night-light toggle is session-scoped; persistent-off follow-up
The menu/Waybar toggle stops hyprsunset for the session only — it's
WantedBy=graphical-session.target, so the next login auto-starts it and the
off-state doesn't persist (deliberate "true colours for now" behaviour).
Record the persistent-off option (a state marker honoured via the unit's
ConditionPathExists) as an optional follow-up so it isn't forgotten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:29:54 +01:00
Bernardo Magri
5c43a93285 feat(nightlight): menu + Waybar on/off toggle
Add a runtime toggle for the scheduled blue-light filter, the remaining
optional item on the night-light roadmap entry.

nomarchy-nightlight (nightlight.nix, always on PATH) starts/stops the
hyprsunset *service* rather than poking its runtime gamma: `systemctl
--user is-active` is the single source of truth, the screen restores to
true colours when stopped, and it lets you force the filter off for
colour-sensitive work (even at night) and back on without touching config.
It self-gates — `status` prints nothing when the unit is absent — so the
indicator hides itself when night-light isn't enabled.

- Menu: `nomarchy-menu nightlight` toggles it; a self-gated row in the
  System submenu (shown only when the hyprsunset unit exists).
- Waybar: a self-gating `custom/nightlight` indicator (moon = on, sun =
  off), in the generated bar and both summer whole-swap themes for parity.

Verified: home generation builds, generated waybar config carries
custom/nightlight, the menu has the dispatcher case + System row, both
scripts pass bash -n, and status self-gates to empty with no unit. Pending
an on-machine check that stopping hyprsunset cleanly restores the gamma.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:23:12 +01:00
Bernardo Magri
09c308b93c chore(boot): default boot.zfs.forceImportRoot = false (silence eval warning)
Nomarchy roots are BTRFS, never ZFS, but the zfs module's `true` default for
forceImportRoot emits a warning on every eval/build ("recommended to set it
to false, the new default from 26.11"). Adopt that default early via
mkDefault — silences the noise, future-proofs for 26.11, and a real ZFS-root
downstream can still force it back on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:10:28 +01:00
Bernardo Magri
bdf20f2d8e feat(hardware): xe-aware GuC, class-based NPU detect, latestKernel + VM test
Harden the hardware-enablement broad seed for newer hardware:

- xe vs i915: intel.guc emits the i915 param (i915.enable_guc=3), which the
  newer `xe` driver (Lunar Lake / Battlemage / Panther Lake, recent Xe GPUs)
  ignores — it enables GuC by default. hardware-db.sh now reads the in-use
  GPU driver (lspci -k) and writes `intel.guc = false` on xe hardware, so the
  toggle isn't a misleading no-op there.

- NPU detection by PCI accelerator class [1200] (+ keywords), attributing the
  vendor, instead of a fixed device-ID list — so new NPUs (e.g. Panther Lake)
  are caught without a code change. Known IDs kept as a fallback/reference.

- Kernel awareness: a nomarchy.hardware.latestKernel escape hatch
  (linuxPackages_latest) for very-new hardware whose drivers only just landed,
  and a build-time warning when npu.enable predates the shipped kernel
  (amdxdna needs 6.14+, intel_vpu wants recent). Downstream default is 6.18,
  which already carries amd-pstate + amdxdna; the reference host pins latest.

- VM test (checks.hardware-toggles, pkgs.testers.runNixOSTest): boots a
  minimal VM with the toggles on and asserts the config landed — amd_pstate
  + i915.enable_guc in /proc/cmdline, fprintd.service present, pam_fprintd in
  /etc/pam.d/sudo. Passing. (Hardware behaviour still needs bare metal.)

Docs: README + template note latestKernel; ROADMAP records the hardening.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 20:13:31 +01:00
Bernardo Magri
c57d26864e feat(hardware): nomarchy.hardware.* enablement beyond nixos-hardware
The nixos-hardware "common-*" profiles the installer selects cover the
basics (microcode, the Intel/AMD VA-API media stack, weekly fstrim), and
power.nix adds thermald + power-profiles-daemon. This adds the gap above
them, generically and detected at install time.

New modules/nixos/hardware.nix exposes a vendor-keyed nomarchy.hardware.*
surface — broadly-beneficial bits default ON when the vendor is detected
(opt-out), heavy/experimental bits behind opt-in toggles:
  - intel.enable -> GuC/HuC (i915.enable_guc=3); intel.computeRuntime
    (opt-in: intel-compute-runtime + vpl-gpu-rt)
  - amd.enable -> amd_pstate=active + radeonsi VA-API env; amd.rocm.enable
    + amd.rocm.gfxOverride (opt-in: ROCm HIP/OpenCL)
  - fingerprint.enable -> fprintd; fingerprint.pam (opt-in: login + sudo)
  - npu.enable (opt-in/experimental) -> the in-kernel driver
    (amdxdna/intel_vpu) keyed by vendor; userspace runtime is BYO

hardware-db.sh now detects Intel/AMD, a fingerprint reader (libfprint USB
vendor IDs) and an NPU (Intel VPU / AMD XDNA PCI IDs), emitting NOMARCHY
lines the installer bakes into system.nix — safe defaults active, opt-ins
commented. Audited against the commons to avoid double-setting.

Verified: flake check green; a toggles-on build has amd_pstate=active +
i915.enable_guc=3 in kernel-params and ships fprintd.service; detection
runs correctly on the (AMD Ryzen-AI) dev machine. On-hardware verification
of the AMD/NPU/Intel-compute runtime bits is still pending.

Docs: template commented examples, README option table, ROADMAP status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:49:39 +01:00
Bernardo Magri
019fdfc8bb feat(waybar): drop redundant network module, enlarge power-profile glyph
The network/wifi indicator duplicated nm-applet, which already sits in the
tray (services.network-manager-applet.enable), so remove it from the bar.
And the power-profile speedometer glyph rendered noticeably smaller than
the neighbouring indicators, so bump its font-size (+3pt generated; 20px
summer-day, 17px summer-night).

Applied to the generated bar (waybar.nix) and both whole-swap themes
(summer-day/night waybar.{jsonc,css}) for parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:13:20 +01:00
Bernardo Magri
c2281dbc61 fix(waybar): launch from exec-once, not a systemd user service
Waybar vanished after a logout→login (no reboot): the bar's systemd user
service landed in `failed`. HM's Hyprland integration cycles
hyprland-session.target on every launch (exec-once stop && start), which
re-pulls WantedBy units — including waybar.service — very early, before
Hyprland's IPC socket is ready. Waybar's hyprland/workspaces & window
modules can't connect, so it exits non-zero and is never retried. A cold
boot's extra latency hid the race, so it only bit on warm relogins. The
non-IPC session services (swaync/hypridle/hyprsunset) survive the same
cycling fine, which is why night-light kept working.

Launch waybar from Hyprland's own exec-once instead (systemd.enable =
false). exec-once entries are dispatched once the compositor is up, so the
IPC socket is ready — the same reliable pattern awww-daemon, the wallpaper
sync and the keyboard watcher already use across relogins. Since the unit
no longer exists to be restarted on a home-manager switch, nudge the
running bar to re-read its rebuilt config/style with SIGUSR2 from
nomarchy-theme-sync so theme switches still reapply the bar live.

Found on the Latitude 5410 hardware sweep (systemctl --user status showed
waybar.service loaded/failed; manual start worked).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:00:39 +01:00
Bernardo Magri
b4fe52261b fix(keyboard): isolate new-keyboard layout to its own device
The interactive new-keyboard picker applied the chosen layout with
`hyprctl switchxkblayout <device> <index>`, which flips the *shared*
layout for every keyboard without its own per-device config — so picking
a layout for an external board dragged the built-in keyboard along, and
the change stuck after unplug (it's global state, not device-bound).
Compounding it, the candidate `keyboard.layouts` pool was merged into
`input.kb_layout`, loading those layouts onto the session keyboard too.

Apply the choice as a per-device `hyprctl keyword device[<name>]:kb_layout`
instead — the runtime twin of the declarative `keyboard.devices` blocks —
so it isolates that one keyboard and never touches the built-in board.
Keep the candidate pool out of `input.kb_layout` (session keyboard carries
only `keyboard.layout`); the pool is for external boards, applied per-device.

Also put `nomarchy-keyboard-watch` on PATH when enabled (it was store-path
only via exec-once, so undiscoverable) for debugging.

Found on the Latitude 5410 hardware sweep; picker itself verified working.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 17:47:19 +01:00
Bernardo Magri
82659566ac chore: untrack config.rasi (scratch reference committed by mistake)
config.rasi was the rofi theme dropped in to show the desired style; it's
not part of the distro. Remove it from tracking (the file stays on disk,
untracked). Its content informed 994025f and remains in that history.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 22:12:02 +01:00
Bernardo Magri
994025f0c8 feat(rofi): real icons, zebra rows, content-fit menus across all themes
Reworked the menu look, palette-driven so it lands on every theme:

  - Real Papirus icons in place of Nerd Font glyphs: a row() helper emits
    rofi's per-row icon protocol, and the five static menus (root, Tools,
    System, Power, Capture) map each entry to a freedesktop icon name
    (verified present in Papirus). Entry text is now clean ("Apps"), the
    case-matching is unchanged.
  - Larger icons via a new t.ui.iconSize knob (36px, generated theme); the
    summer whole-swaps keep their 40px identity.
  - Rounded, roomy rows with subtle zebra striping: alternate rows lift to
    @surface (a faint bg0 wash on the summer cream/slate windows), the
    selected row to @accent — using rofi's normal/alternate/selected row
    parities.
  - Content-fit windows: fixed-num-lines=false so a 6-entry menu stops
    reserving 8 rows of empty space (the launcher still fills + scrolls).
    Dropped the fixed height:50% from the summer .rasi so they size to
    content too — which also unhid their clipped Power/Keybindings rows.
  - Title-cased the menu prompts.

Verified by building the HM config and rendering the generated theme plus
both summer whole-swaps headlessly (sway + grim): icons resolve, zebra
shows, every menu hugs its rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 22:11:25 +01:00
Bernardo Magri
b0b2a4d732 docs(services): note the restic-nomarchy list/restore wrapper
VM-verified the full batch of opt-in service toggles (libvirt, docker, obs
+ v4l2loopback, ollama, printing, openrgb, wireshark, adb, gamemode,
kdeconnect, restic) in a headless nixosTest — all green, no module bugs.

The one thing worth surfacing: services.restic ships a pre-configured
`restic-nomarchy` wrapper (repository + passwordFile baked in) for listing
and restoring snapshots. Mention it in the option description and README so
the restore path is discoverable — a backup you can't restore is half a
feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:18:49 +01:00
Bernardo Magri
964a5c98b7 feat(services): openrgb + restic toggles; close out the opt-in services item
Finishing the four remaining "opt-in services" candidates — but two of
them aren't service-shaped, so the package-vs-toggle principle applies:

  - Nextcloud client  → a bare tray app; already in the app-suite menu,
                        not a service.
  - WireGuard         → NetworkManager (on distro-wide) imports .conf
                        tunnels natively, so there's no daemon to "just
                        enable". Only the wg/wg-quick CLI is missing →
                        wireguard-tools added to the app-suite menu.

The two that ARE config-backed become nomarchy.services.* toggles:

  - openrgb → services.hardware.openrgb (daemon + device udev rules).
  - restic  → a small scaffolded surface (repository / passwordFile /
              paths) over services.restic.backups: daily timer, 7/4/6
              retention, --exclude-caches, repo auto-init. Asserts that
              repository + passwordFile are set when enabled. passwordFile
              is typed `str`, not `path`, so a flake-relative secret can't
              be copied into the world-readable store.

README option table and ROADMAP (services now "Seventeen shipped";
cloud-sync / networking / backup / devices areas updated) + the downstream
system.nix examples. Both branches eval-verified; the restic assertions
confirmed firing when repo/password are unset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:02:20 +01:00
Bernardo Magri
c45fb80b79 feat(services): full optional app menu + 7 more opt-in service toggles
Make the whole workstation menu available, all off/commented by default.

home.nix: the bare GUI apps become a categorized commented menu (~50
suggestions across browsers, comms, office, notes, code, graphics, A/V,
media, gaming, files/sync, security, system, local AI) — uncomment to add.

services.nix: the members that need system config (not just a package)
become opt-in nomarchy.services.* toggles, each with a commented example
in the downstream system.nix:
  - docker     → rootful Docker + user in docker group; asserts against the
                 podman docker-compat (both provide the `docker` command)
  - kdeconnect → programs.kdeconnect (opens phone-pairing ports)
  - gamemode   → Feral GameMode daemon
  - adb        → android-tools (programs.adb was removed; systemd ≥258 does
                 the device uaccess udev rules, so no group needed)
  - wireshark  → Qt GUI + user in wireshark group (capture without root)
  - ollama     → local LLM runtime on 127.0.0.1:11434
  - printing   → CUPS + Avahi/mDNS network printer discovery

README option table and ROADMAP (services item now "Fifteen shipped";
local-AI / containers / gaming / devices areas marked) updated. All seven
branches eval-verified with the toggles forced on; the docker/podman
assertion confirmed firing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 20:52:38 +01:00
Bernardo Magri
0c668c2eb1 feat(apps): starter workstation app suite + steam/libvirt/obs services
The "default application suite" roadmap item, but deliberately NOT a
nomarchy.apps.* option surface: a bare package install has no config to
put behind a toggle, so a thin `mkIf x { systemPackages = [p]; }` only
reinvents what a package list already gives. Instead the suite is a
curated home.packages list in templates/downstream/home.nix (extending
the existing `# firefox` convention) — active staples (libreoffice,
vscode, gimp, inkscape, mpv, amberol) plus commented suggestions. The
opt-out is deleting a line; it also keeps the apps out of the live ISO
automatically (the ISO never scaffolds from the template).

The members that DO need system config become nomarchy.services.* opt-in
toggles instead (config behind the switch, like the existing services):
  - steam   → programs.steam (32-bit stack, controller udev, RP ports)
  - libvirt → libvirtd + virt-manager, login user added to libvirtd group
  - obs     → obs-studio + a v4l2loopback virtual camera (exclusive_caps=1)
              selectable as a webcam in Zoom/Teams

README option table, ROADMAP (services item now "Eight shipped"; suite
item marked done with the no-surface rationale) and the downstream
system.nix examples updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 20:45:10 +01:00
Bernardo Magri
1f4bd3d5fa docs(readme): document the curated shell aliases in §5 Day-to-day
List the on-by-default zsh aliases (navigation, git, nix, misc) in the
Day-to-day section so they're discoverable from the README, not just by
running `alias`. Note the doc location in the roadmap item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:48:22 +01:00
Bernardo Magri
017bb1aa41 feat(shell): curated git/nix/navigation aliases on by default
Extend the zsh shellAliases in shell.nix with a curated, on-by-default
set: navigation (.., ..., ....), a git block (g, gst, ga/gaa, gc/gcm,
gco/gsw, gb, gd/gds, gl/glg, gp/gpl, gf), a nix block (ns, nr, nfu, nfc,
nsearch, ngc), and path/reload. Same restraint as rg/fd: short where it
helps but never shadowing a real binary — the git set keeps off gs
(ghostscript), and the system/home rebuilds keep their sys-update /
home-update names rather than cryptic aliases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:46:17 +01:00
Bernardo Magri
b856bb93e8 docs(roadmap): close the sanitize file-audit; menu grouping is ongoing
The repo-organization re-check found nothing to move: no orphaned modules
(keybinds.nix is a data import, not a HM module), no dead-code markers or
editor/backup cruft, every themes/<slug>.json pairs with its optional
overrides dir, and hosts/{default,live} are the reference + ISO hosts.
Recorded the one open decision: no formatter is declared and the .nix
hand-formatting is deliberate, so adopting nixfmt is a maintainer call,
left untouched. Also marked the menu category grouping a standing
convention — new menu-facing features get placed in the right submenu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:40:50 +01:00
Bernardo Magri
e0ee5b9e7e feat(menu): group the rofi picker into Tools / System submenus
The root picker had grown to 16 flat entries. Collapse it to six —
Apps · Theme · Tools › · System › · Power · Keybindings — with Tools
(Calculator, Clipboard, Emoji, Files, Web, Capture, Ask) and System
(Network, Bluetooth, DND, plus the self-gated Snapshots / Power-profile)
as submenus the dispatcher routes via `nomarchy-menu tools|system`. Each
submenu ends in a ← Back entry that re-opens the root. The direct
SUPER+CTRL+<mnemonic> binds still reach the leaves straight (bypassing
the menu), and the gated entries still hide when unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:35:03 +01:00
Bernardo Magri
f68b245d92 feat(services): add podman, flatpak (+ Flathub) and pika backup
Extend nomarchy.services.* with three more opt-in toggles:
- podman: rootless containers, `docker` aliased to it, container DNS, and a
  subuid/subgid range for the login user.
- flatpak: services.flatpak + a oneshot that adds the Flathub remote
  system-wide (idempotent, retries when online).
- pika: ships pika-backup, the GUI for scheduled Borg backups.
Commented examples in the downstream system.nix template, per convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:15:45 +01:00
Bernardo Magri
fb748c217c feat(services): opt-in Tailscale + Syncthing (nomarchy.services.*)
Start the opt-in services surface: nomarchy.services.{tailscale,syncthing},
off by default, in modules/nixos/services.nix. Tailscale ships the daemon
(authenticate with `sudo tailscale up`); Syncthing runs as the login user
with its GUI on 127.0.0.1:8384 (overrideDevices/Folders off so GUI-managed
folders survive rebuilds). username is read lazily, only when Syncthing is
on. Commented examples in the downstream system.nix template per the opt-in
convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:08:41 +01:00
Bernardo Magri
8d3911de1a docs(template): ship opt-in features as commented examples downstream
Convention: an off-by-default feature that needs nomarchy.* settings in the
downstream files should appear commented-out in templates/downstream/{home,
system}.nix, so a new install enables it by uncommenting + tweaking rather
than hunting the docs for the option name. Apply it now: night light,
nomarchy.monitors, per-device keyboard layouts (home.nix) and power
management (system.nix). Also fix the stale keyboard.layout note
(console/LUKS follow xkb automatically now). Documented in README S6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:53:13 +01:00
Bernardo Magri
6ad80c045e feat(nightlight): scheduled blue-light filter via hyprsunset
Add nomarchy.nightlight (opt-in): a scheduled colour-temperature shift via
the HM services.hyprsunset module -- warm (.temperature, default 4000K) at
night, identity (no shift) by day, switching at .sunset / .sunrise. Two
time-based hyprsunset profiles, so hyprsunset handles the schedule and the
on-login state. New modules/home/nightlight.nix, imported in the home module.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:37:03 +01:00
Bernardo Magri
54c6e100fb docs(roadmap): organize the menu picker into category submenus
The root picker has grown into a long flat list; group it into category
submenus (System / Tools / Look & Feel / Power) with the dispatcher routing
nomarchy-menu <category> to its leaf modules, keeping the SUPER+CTRL+<mnemonic>
binds as direct fast paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:29:30 +01:00
Bernardo Magri
eb76bd61b4 docs(roadmap): note btrfs-assistant segfault; snapper backend VM-verified
A VM regression test caught that btrfs-assistant 2.2 segfaults on launch
inside libbtrfsutil.so.1.4.0 -- a library ABI mismatch in nixpkgs 26.05, not
a GL/VM artifact (crashes with software GL and every Qt platform; the fault
is in a btrfs library). The snapshot menu wiring is correct and the app is
installed, but the GUI doesn't open. The snapshot backend is unaffected and
is itself VM-verified (full BTRFS+LUKS install: root+home snapper configs, a
/home timeline snapshot, /home/.snapshots a real subvolume, the oneshot
Result=success). Keep the entry, document the bug pending a nixpkgs fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:43:46 +01:00
Bernardo Magri
71f37bfc30 feat(keyboard): interactive new-keyboard layout prompt + persist
The runtime-remember complement to nomarchy.keyboard.devices. Set
nomarchy.keyboard.layouts (candidate layouts) and a nomarchy-keyboard-watch
daemon runs (exec-once): it polls hyprctl devices, and when a keyboard
connects after login that isn't declared and hasn't been chosen before, it
pops a rofi layout picker, applies the choice via hyprctl switchxkblayout (an
index into the candidate set carried by input.kb_layout), and remembers it
per-device in ~/.local/state -- re-applied silently on later reconnects. The
boot-time set (incl. the built-in keyboard) is never prompted.

Pure bash + jq/hyprctl/rofi (all in the session PATH); state lives outside
the flake (a stateful runtime piece by design). Eval + bash-syntax verified;
the hotplug behaviour needs an on-hardware test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:43:59 +01:00
Bernardo Magri
69f959365e docs(roadmap): interactive new-keyboard layout prompt + persist
A runtime-remember complement to the declarative nomarchy.keyboard.devices:
a daemon watches Hyprland's socket, pops a rofi layout picker when an unknown
keyboard connects, applies it, and persists per-device so it re-applies on
reconnect — optionally writing the choice back into nomarchy.keyboard.devices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:32:05 +01:00