Compare commits

...

32 Commits

Author SHA1 Message Date
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
Bernardo Magri
aef45b3c8d feat(keyboard): per-device layout for external keyboards
Add nomarchy.keyboard.devices ({ "<hyprctl-device-name>" = { layout; variant; }; })
generating Hyprland `device` blocks that override the session
nomarchy.keyboard.layout for a named keyboard -- e.g. an external board
that's physically a different layout than the laptop's built-in one.
Hyprland applies it whenever that device connects, so the external keyboard
"remembers" its layout the declarative way. A Waybar hyprland/language
indicator shows the active layout, placed only when more than one layout is
in play (a comma in the session layout, or any per-device override) so
single-layout bars stay clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:27:28 +01:00
Bernardo Magri
0897391cfb feat(snapshots): desktop browse/restore + snapshot /home by default
Surface snapper snapshots from the desktop: `nomarchy-menu snapshot` (in the
SUPER+M picker, self-gated) launches btrfs-assistant, the polkit-aware
browse/diff/restore/rollback UI -- safer than a fat-fingerable rofi rollback
for a destructive root-only op. Shipped system-side gated on
nomarchy.system.snapper.

Also snapshot /home by default (hourly 5 / daily 7 / weekly 4) when it's its
own BTRFS subvolume -- the installer only set up snapper for /. A boot
oneshot creates the required /home/.snapshots subvolume if missing, so
already-installed machines get it too (no disko change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:14:05 +01:00
Bernardo Magri
5c83b52727 docs: mark the GTK/Qt color-scheme portal confirmed in a live session
Verified this session in headless QEMU: the org.freedesktop.appearance
color-scheme portal reads 1 on a dark theme and 2 after switching to a light
one, so the dconf value reaches GTK4/Qt apps. Removes the stale "needs a
live session to confirm" note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:47:59 +01:00
Bernardo Magri
0751102915 fix(waybar): show the power-profile indicator in the summer themes
custom/powerprofile only appeared in the generated bar -- its exec was a
writeShellScript store path, which can't go in the summer themes' static
whole-swap waybar.jsonc, so summer-day/night were missing it (the DND bell
already had parity). Promote powerProfileStatus/Cycle to named
writeShellScriptBins on PATH (waybar.nix home.packages) and reference them
by bare name, then add custom/powerprofile to both summer waybar.jsonc +
their waybar.css. Same pattern as the swaync bell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:46:45 +01:00
Bernardo Magri
5c59da73e7 docs(roadmap): full hardware enablement beyond nixos-hardware
nixos-hardware covers basic per-model enablement, but advanced features
(keyed on CPU/GPU generation) are left to the user. Capture a big item:
extend the installer's autodetection + a nomarchy.hardware.* surface to turn
on safe features automatically (opt-out) and gate the heavy/experimental
ones (opt-in), without per-feature fiddling. Worked example on the ThinkPad
T14s (Ryzen 7 PRO 7840U + Radeon 780M): ROCm (gfx1103 override), Ryzen AI
NPU/XDNA, AMD P-State EPP, VA-API, fprintd biometrics (fwupd already ships),
SSD fstrim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:35:07 +01:00
Bernardo Magri
42801548b7 feat(displays): declarative nomarchy.monitors + nwg-displays arranger
Add a per-output monitor schema (name / resolution / position / scale /
transform / mirror / bitdepth / vrr / disable) that generates Hyprland
`monitor` rules, keeping the ,preferred,auto,1 wildcard as the fallback.
Hyprland applies the rules on hotplug, so a declared external/dock output
arranges itself on connect (no kanshi -- it fights Hyprland's own output
management). Default [] -> wildcard only, so no change for existing setups;
set wayland.windowManager.hyprland.settings.monitor directly to override.

nwg-displays ships behind nomarchy.displays.enable (default true) as an
interactive arranger -- a helper to discover values; the declarative config
stays the source of truth (its output file isn't sourced).

Remaining (roadmap): docked/undocked profile switching, workspace-to-monitor
binding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:22:18 +01:00
Bernardo Magri
1b0eeeaf6c fix(idle): don't lock before an encrypted hibernate (was: unlock after)
The previous nomarchy-hibernate-unlock ran `pkill hyprlock` after a
hibernate resume, but killing a Wayland session-lock client without
releasing the lock trips hyprlock's "go to a tty" crash failsafe -- the
compositor keeps the screen locked for safety. So resume showed a hyprlock
error screen instead of unlocking.

Fix it the right way: never engage the lock before an encrypted hibernate
(the LUKS passphrase at resume is the gate). Replace the post-resume unlock
with a nomarchy-lock-before-sleep unit that takes over hypridle's
before_sleep_cmd: it locks on the RAM-resume sleeps (suspend / hybrid-sleep
/ suspend-then-hibernate) always, and on hibernate.target only when the disk
is unencrypted. idle.nix drops before_sleep_cmd accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:15:31 +01:00
a606c0d498 docs: log power-profile Waybar parity as a follow-up
The custom/powerprofile indicator is missing from the whole-swap summer
themes (its exec uses Nix-built script paths that can't go in static
JSON). Note the fix (promote the scripts to named bins on PATH) and the
general "new module → also add to whole-swap themes" rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:12:38 +01:00
cd495d1720 fix(dnd): add the notification bell to the whole-swap summer themes
The DND bell only landed in the generated Waybar config; summer-day and
summer-night ship waybar.jsonc whole-swaps that replace it entirely, so
they'd have shown no bell. Add custom/notification (swaync-client -swb,
left-click panel / right-click DND) to both legacy layouts with their
FontAwesome bell / bell-slash glyphs, and style it alongside the other
right-side modules in each waybar.css.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:08:01 +01:00
dbcb8efd1e feat(dnd): swaync Do-Not-Disturb toggle + Waybar bell indicator
Adds a Do-Not-Disturb story on top of swaync:

- Waybar `custom/notification` module via `swaync-client -swb` — streams
  the notification count + DND state (no polling). Left-click toggles the
  panel, right-click toggles DND; bell-off glyph + muted color when DND is
  on, accent when notifications are waiting.
- `nomarchy-menu dnd` toggle in the root picker and bound to SUPER+CTRL+D
  (so it shows in the SUPER+? cheatsheet). Turning DND off confirms with a
  toast; turning it on is silent — notifications are suppressed and the
  bell-off icon is the cue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:01:11 +01:00
07e467027b feat(nix): silence the "Git tree is dirty" warning on rebuilds
The downstream flake at ~/.nomarchy is a live working tree by design —
nomarchy-theme-sync rewrites theme-state.json on every switch — so Nix's
dirty-tree warning fired on every sys-update/home-update and was just
noise. Default nix.settings.warn-dirty to false (mkDefault, overridable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:53:18 +01:00
9a5473e179 docs: record the branch/release model + sanitize progress
Document the main=dev / v1=release-pointer model in flake.nix (where the
v1 ref is defined) and mark the repo-sanitize roadmap items shipped
(installer prune, option-table reconcile, settings untrack, branch
realignment).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:48:06 +01:00
db6a0ba455 chore: untrack .claude/settings.local.json (machine-local)
It holds this machine's Claude Code permission allowlist — local by
convention (the `.local` suffix; settings.json is the shared one), so it
shouldn't be versioned. Remove from tracking (keeping the working file)
and gitignore it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:45:54 +01:00
8c47716f9a chore(install): stop writing distro-default config into system.nix
console.useXkbConfig and boot.initrd.systemd.enable are set distro-wide
(lib.mkDefault true in modules/nixos/default.nix, since the LUKS-keymap
fix), so the installer rewriting them in the generated system.nix was
redundant. Drop them there and from the offline-pin test fixture in
flake.nix — the nomarchy module still defaults them on, so the closure
(and the pin set) is unchanged; verified the values still resolve true
and `nix flake check` passes. Only the machine-specific xkb layout is
written now. Adjusts the README keyboard-option wording to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:42:36 +01:00
26 changed files with 1398 additions and 460 deletions

View File

@@ -1,284 +0,0 @@
{
"permissions": {
"allow": [
"Bash(grep -i \"\\\\.nix$\")",
"Bash(xargs ls *)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.activationPackage.outPath)",
"Bash(git *)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.default.config.system.activationScripts.home-manager-activate.text)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.installerIsoGraphical.config.system.activationScripts.home-manager-activate.text)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.config.system.build.toplevel.drvPath)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.system.build.toplevel.drvPath)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#homeConfigurations.nomarchy.activationPackage.drvPath)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.default.config.systemd.services.\"home-manager-nomarchy\".serviceConfig.ExecStart)",
"Bash(nix --extra-experimental-features 'nix-command flakes' build .#nixosConfigurations.default.config.system.build.vm --no-link --print-out-paths)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#homeConfigurations.nomarchy.config.xdg.configFile.\"nomarchy/current/background\".source)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.home-files.outPath)",
"Bash(nix --extra-experimental-features 'nix-command flakes' build .#homeConfigurations.nomarchy.config.home-files --no-link --print-out-paths)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#homeConfigurations.nomarchy.config.systemd.user.startServices)",
"Bash(xxd)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#packages.x86_64-linux.allThemeVariants.drvPath)",
"Bash(chmod +x /home/bernardo/Projects/Coding/Nomarchy/themes/engine/scripts/nomarchy-themes-prebuild)",
"Bash(nix --extra-experimental-features 'nix-command flakes' build .#homeConfigurations.nomarchy.activationPackage --no-link --print-out-paths)",
"Bash(nix --extra-experimental-features 'nix-command flakes' build .#homeConfigurations.nomarchy.config.home.packages.outPath)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.home-path.outPath)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --expr '\\(builtins.readDir ./themes/engine/scripts\\)' --json)",
"Bash(nix-build -I nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos /home/bernardo/Projects/Coding/Nomarchy/flake.nix --argstr attr nixosConfigurations.installerIsoGraphical)",
"Bash(awk '{print $NF}')",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.hardware.pulseaudio.enable .#nixosConfigurations.installerIsoGraphical.config.services.pipewire.enable .#nixosConfigurations.installerIsoGraphical.config.services.pipewire.pulse.enable)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.services.displayManager.sddm.enable .#nixosConfigurations.installerIsoGraphical.config.services.displayManager.defaultSession .#nixosConfigurations.installerIsoGraphical.config.programs.hyprland.enable)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --json .#nixosConfigurations.installerIsoGraphical.config.users.users.nixos.extraGroups)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.hardware.pulseaudio.enable)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.services.pipewire.enable)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.services.pipewire.pulse.enable)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.programs.hyprland.enable)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval '.#nixosConfigurations.installerIsoGraphical.config.__TRACKED_VAR__')",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.installerIsoGraphical.config.home-manager.users.nixos.home.packages --apply 'builtins.length')",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.config.home-manager.users.nomarchy.home.packages --apply 'builtins.length')",
"Bash(chmod +x /home/bernardo/Projects/Coding/Nomarchy/features/scripts/utils/nomarchy-test-live-iso)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --json .#nixosConfigurations.installerIsoGraphical.config.boot.initrd.kernelModules)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#homeConfigurations.nomarchy.config.programs.waybar.systemd.enable)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.systemd.user.services --apply 'svcs: builtins.concatStringsSep \"\\\\n\" \\(builtins.attrNames svcs\\)')",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.gtk.iconTheme.package.pname)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw nixpkgs#everforest-gtk-variant.pname)",
"Bash(nix --extra-experimental-features 'nix-command flakes' search nixpkgs everforest)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.everforest-gtk-variant.pname)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.everforest-gtk-theme.pname)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.everforest-gtk-variant)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --expr 'let pkgs = import \\(builtins.getFlake \"/home/bernardo/Projects/Coding/Nomarchy\"\\).inputs.nixpkgs { system = \"x86_64-linux\"; config.allowUnfree = true; }; in builtins.attrNames \\(pkgs // {} \\)')",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --expr 'let pkgs = import \\(builtins.getFlake \"/home/bernardo/Projects/Coding/Nomarchy\"\\).inputs.nixpkgs { system = \"x86_64-linux\"; config.allowUnfree = true; }; in builtins.concatStringsSep \"\\\\n\" \\(builtins.filter \\(n: builtins.match \".*[Ee]verforest.*\" n != null\\) \\(builtins.attrNames pkgs\\)\\)' --raw)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#homeConfigurations.nomarchy.config.gtk.iconTheme.name)",
"Bash(bash -n /home/bernardo/Projects/Coding/Nomarchy/installer/install.sh)",
"Bash(bash -n /home/bernardo/Projects/Coding/Nomarchy/installer/hardware-db.sh)",
"Read(//tmp/**)",
"Bash(bash -c ' *)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#overlays.default --apply 'o: toString \\(builtins.typeOf o\\)')",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#overlays.default --apply 'o: \"overlay present: \" + builtins.typeOf o')",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.default.config.system.nixos.distroName)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.default.config.system.nixos.distroId)",
"Bash(nix-instantiate --parse /home/bernardo/Projects/Coding/Nomarchy/installer/disko-golden.nix)",
"Bash(nix --extra-experimental-features 'nix-command flakes' build .#nixosConfigurations.installerIsoGraphical.config.system.build.toplevel --no-link --print-out-paths)",
"Bash(bash /home/bernardo/Projects/Coding/Nomarchy/installer/install.sh --help)",
"Bash(bash -c 'echo \"HOSTNAME=$HOSTNAME\"')",
"Bash(bash -c 'echo \"HOSTNAME default=$HOSTNAME\"')",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --raw .#nixosConfigurations.installerIsoGraphical.config.services.getty.helpLine)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.voxtype.pname)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval .#nixosConfigurations.default.pkgs.opencode.pname)",
"Bash(nix --extra-experimental-features 'nix-command flakes' build .#nixosConfigurations.default.config.system.build.vm --no-link)",
"Bash(nix log *)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --json --impure --expr ' *)",
"Bash(cat)",
"Bash(chmod +x /tmp/theme_comparison.sh)",
"Bash(/tmp/theme_comparison.sh)",
"Bash(xargs -I {} dirname {})",
"Bash(xargs -I {} basename {})",
"Bash(chmod +x /tmp/color_compare.sh)",
"Bash(bash /tmp/color_compare.sh)",
"Bash(nix --extra-experimental-features \"nix-command flakes\" eval --raw 'github:nix-community/disko/latest#packages.x86_64-linux.disko.outPath')",
"Bash(find /nix/store/*disko*/lib/disko -name \"luks*\")",
"Bash(nix flake *)",
"Bash(nix-instantiate --parse /home/bernardo/Projects/Coding/Nomarchy/flake.nix)",
"Bash(nix eval *)",
"Bash(bash installer/install.sh --dry-run)",
"Bash(sudo bash *)",
"Bash(chmod +x /home/bernardo/Projects/Coding/Nomarchy/core/system/scripts/nomarchy-env-update)",
"Bash(bash -n /home/bernardo/Projects/Coding/Nomarchy/core/system/scripts/nomarchy-env-update)",
"Bash(sed -n '1450,1500p' /home/bernardo/Projects/Coding/Nomarchy/installer/install.sh)",
"Bash(grep -A 5 'nixosConfigurations.$HOSTNAME' /home/bernardo/Projects/Coding/Nomarchy/installer/install.sh)",
"Bash(mkdir -p /tmp/globtest)",
"Bash(touch nvme0n1 *)",
"Bash(shopt -s nullglob)",
"Bash(bash -n installer/install.sh)",
"Bash(nix --extra-experimental-features \"nix-command flakes\" eval .#nixosConfigurations.nomarchy-live.config.system.build.toplevel.outPath)",
"Bash(nix --extra-experimental-features \"nix-command flakes\" eval --impure --expr 'let f = import ./core/system/impermanence.nix; in \\(f { config = { nomarchy.system.impermanence = { enable = true; mainLuksName = \"crypted_main\"; }; }; lib = \\(import <nixpkgs> {}\\).lib; pkgs = null; inputs = { impermanence.nixosModules.impermanence = {}; }; }\\).config.boot.initrd.postDeviceCommands')",
"Bash(nix --extra-experimental-features \"nix-command flakes\" eval .#nixosConfigurations.nomarchy-installer.config.system.build.toplevel.outPath)",
"Bash(nix --extra-experimental-features \"nix-command flakes\" eval .#nixosConfigurations.nomarchy-live.config.nomarchy.system.theme)",
"Bash(chmod +x *)",
"Bash(./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md)",
"Bash(awk '/^## Missing references/,/^## Menu items/' docs/SCRIPTS.md)",
"Bash(bash -n bin/utils/nomarchy-docs-scripts)",
"Bash(bash -n features/scripts/utils/nomarchy-docs-scripts)",
"Bash(bash -n themes/engine/scripts/nomarchy-theme-next)",
"Bash(grep '`unused?`' docs/SCRIPTS.md)",
"Bash(awk -F'|' '{print $2}')",
"Bash(awk *)",
"Bash(grep -c '`unused?`' docs/SCRIPTS.md)",
"Bash(grep -n '`unused?`' docs/SCRIPTS.md)",
"Bash(grep '^| `__TRACKED_VAR__` ' docs/SCRIPTS.md)",
"Bash(nix --extra-experimental-features 'nix-command flakes' flake check --no-build)",
"Bash(grep -c '`kept`' docs/SCRIPTS.md)",
"Bash(nix shell *)",
"Bash(nix shell 'nixpkgs#shellcheck' --command bash -c ' *)",
"Bash(./bin/utils/nomarchy-docs-scripts --out /tmp/SCRIPTS.regen.md)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --expr ' *)",
"Bash(sed -n '100,108p' core/home/options.nix)",
"Bash(sed -n '185,195p' core/system/options.nix)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --json --expr ' *)",
"Bash(bash -n *)",
"Bash(nix --extra-experimental-features 'nix-command flakes' eval --impure --raw --expr ' *)",
"Bash(grep '^| `nomarchy-manual`' docs/SCRIPTS.md)",
"Bash(nix *)",
"Bash(sudo -n cat /etc/environment)",
"Bash(systemctl --user show-environment)",
"Bash(./bin/utils/nomarchy-docs-keybindings --out /tmp/keybindings-fresh.md)",
"Bash(bash bin/utils/nomarchy-docs-keybindings --out /tmp/keybindings-fresh.md)",
"Bash(nix-shell -p shellcheck --run \"shellcheck --severity=error features/scripts/utils/nomarchy-menu features/scripts/utils/nomarchy-launch-screensaver\")",
"Bash(./bin/utils/nomarchy-docs-keybindings --out docs/KEYBINDINGS.md)",
"Bash(grep -rEho 'jq[^\"]*--arg [a-zA-Z]+|\\\\.[a-zA-Z][a-zA-Z_0-9]+ = \\\\$|jq -r .\\\\.[a-zA-Z]+' features/scripts core/system/scripts themes/engine/scripts)",
"Read(//home/bernardo/.config/mako/**)",
"Read(//home/bernardo/.config/nomarchy/default/hypr/**)",
"Read(//home/bernardo/.config/nomarchy/current/theme/**)",
"Read(//etc/nixos/**)",
"Bash(sudo git -C /etc/nixos log --oneline -3)",
"Bash(PATH=/home/bernardo/Projects/Coding/Nomarchy/features/scripts/utils:/home/bernardo/Projects/Coding/Nomarchy/themes/engine/scripts:__TRACKED_VAR__ bash /home/bernardo/Projects/Coding/Nomarchy/features/scripts/utils/nomarchy-installed-summary)",
"Bash(grep *)",
"Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); r=d['results'][0]['extensions'][0]; print\\('version:', r['versions'][0]['version']\\); print\\('displayName:', r['displayName']\\); print\\('publisher:', r['publisher']['publisherName']\\); print\\('extName:', r['extensionName']\\)\")",
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); [print\\(x['file'],'=',x['value']\\) for x in d]\")",
"Bash([ -f \"core/home/config/nomarchy/default/hypr/apps/$f.conf\" ])",
"Bash([ -f \"core/home/config/nomarchy/default/hypr/bindings/$f.conf\" ])",
"Bash([ -f \"core/home/config/nomarchy/default/hypr/$f.conf\" ])",
"Bash([ -f \"features/desktop/hyprland/config/$f.conf\" ])",
"Bash(timeout 580 ./bin/utils/nomarchy-eval-matrix)",
"Bash(break)",
"Bash(sed -n 1,45p __TRACKED_VAR__/nix/package.nix)",
"Bash(sed -n 1,50p __TRACKED_VAR__/nix/modules/home-manager.nix)",
"Bash(python3 -c 'import json,sys; d=json.load\\(sys.stdin\\); print\\('\\\\''top path:'\\\\'', d.get\\('\\\\''path'\\\\'','\\\\''?'\\\\''\\)\\); inp=d.get\\('\\\\''inputs'\\\\'',{}\\); print\\('\\\\''inputs:'\\\\'', list\\(inp.keys\\(\\)\\)\\); print\\('\\\\''walker present:'\\\\'', '\\\\''walker'\\\\'' in inp\\); *)",
"Bash(echo \"exit: $?\")",
"Bash(echo 'output: [__TRACKED_VAR__]')",
"Bash(time ./bin/utils/nomarchy-eval-matrix)",
"Bash(python3 -c \"import json,sys; [print\\(x\\) for x in sorted\\(json.load\\(sys.stdin\\)\\)]\")",
"Read(//dev/**)",
"Bash(command -v qemu-system-x86_64 qemu-kvm)",
"Bash(echo \"BUILD_EXIT=$?\")",
"Bash(rm -f /tmp/nomarchy-mon.sock)",
"Bash(mkdir -p /tmp/nomarchy-vmrun)",
"Bash(QEMU_OPTS='-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -vga none' /tmp/nomarchy-vm/bin/run-nomarchy-vm)",
"Bash(echo \"VM_EXIT=$?\")",
"Bash(magick /tmp/shot.png -crop 5120x130+0+0 +repage /tmp/shot-top.png)",
"Bash(magick /tmp/shot.png -crop 1400x420+0+440 +repage /tmp/shot-term.png)",
"Bash(/nix/store/wv8bpzriikv65xnd1vciqpq7rnr8h2q2-bash-5.3p3/bin/bash -c 'compgen -G \"/etc/*\" >/dev/null 2>&1 && echo \"compgen WORKS\" || echo \"compgen MISSING \\(progcomp disabled\\)\"')",
"Bash(compgen -G \"/etc/*\")",
"Bash(/nix/store/wv8bpzriikv65xnd1vciqpq7rnr8h2q2-bash-5.3p3/bin/bash -c 'shopt -s nullglob; a=\\(/etc/hos*\\); echo \"nullglob ok: ${#a[@]} match\\(es\\)\"')",
"Bash(mkdir -p /tmp/nomarchy-vmrun2)",
"Bash(pgrep -f qemu-system)",
"Bash(rm -rf /tmp/nomarchy-vmrun3)",
"Bash(mkdir -p /tmp/nomarchy-vmrun3)",
"Bash(echo \"exit=$?\")",
"Bash(socat - unix-connect:/tmp/nomarchy-mon.sock)",
"Bash(magick /tmp/shot3.ppm /tmp/shot3.png)",
"Bash(magick identify *)",
"Bash(echo \"captured -> $\\(magick identify -format '%wx%h' /tmp/shot3.png\\)\")",
"Bash(magick /tmp/shot3.png -crop 5120x150+0+0 +repage /tmp/shot3-top.png)",
"Bash(pkill -x qemu-system-x86)",
"Bash(pkill -f \"qemu-system-x86_64 -machine\")",
"Bash(rm -rf /tmp/nomarchy-vmrun4)",
"Bash(mkdir -p /tmp/nomarchy-vmrun4)",
"Bash(echo \"boot-exit=$?\")",
"Bash(magick /tmp/shot4.ppm /tmp/shot4.png)",
"Bash(echo \"captured -> $\\(magick identify -format '%wx%h' /tmp/shot4.png\\)\")",
"Bash(magick /tmp/shot4.png -crop 5120x160+0+0 +repage /tmp/shot4-top.png)",
"Bash(rm -rf /tmp/vmrun_final)",
"Bash(mkdir -p /tmp/vmrun_final)",
"Bash(magick /tmp/shot5.ppm /tmp/shot5.png)",
"Bash(echo \"captured -> $\\(magick identify -format '%wx%h' /tmp/shot5.png\\)\")",
"Bash(magick /tmp/shot5.png -crop 5120x170+0+0 +repage /tmp/shot5-top.png)",
"Bash(rm -rf /tmp/vmssh)",
"Bash(mkdir -p /tmp/vmssh)",
"Bash(QEMU_OPTS='-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -vga none' QEMU_NET_OPTS=hostfwd=tcp::2222-:22 /tmp/nomarchy-vm-ssh/bin/run-nixos-vm)",
"Bash(command -v expect)",
"Read(//home/bernardo/.ssh/**)",
"Bash(QEMU_OPTS='-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -vga none' QEMU_NET_OPTS=hostfwd=tcp::2222-:22 /tmp/nomarchy-vm-ssh/bin/run-nomarchy-vm)",
"Bash(echo \"vm-exited rc=$?\")",
"Bash(pkill -f \"run-nomarchy-vm\")",
"Bash(rm -rf /tmp/vmtheme)",
"Bash(mkdir -p /tmp/vmtheme)",
"Bash(echo \"vm exited rc=$?\")",
"Bash(magick /tmp/latte.ppm -resize 1700 /tmp/latte.png)",
"Bash(echo \"saved $\\(magick identify -format '%wx%h' /tmp/latte.png\\)\")",
"Bash(./bin/utils/nomarchy-eval-matrix)",
"Bash(rm -f /tmp/nomarchy.qcow2 /tmp/vm-boot.log)",
"Bash(NIX_DISK_IMAGE=/tmp/nomarchy.qcow2 QEMU_OPTS='-display none -m 4096' /tmp/vm-verify-result/bin/run-nomarchy-vm)",
"Bash(echo \"VM PID $!\")",
"Bash(pkill -f 'qemu-system-x86_64 -machine accel=kvm.*nomarchy')",
"Bash(rm -f /tmp/vm-verify.nix /tmp/nomarchy.qcow2 /tmp/vm-boot.log /tmp/vm-verify-result)",
"Bash(pkill -9 -f 'qemu-system')",
"Bash(rm -f /tmp/nomarchy.qcow2)",
"Bash(python3 -)",
"Bash(sed -n '206,228p' features/scripts/utils/nomarchy-menu)",
"Bash(pkill -9 -f 'run-nixos-vm\\\\|qemu-system-x86_64.*nomarchy')",
"Bash(rm -f /tmp/nomarchy.qcow2 /tmp/vm-verify.nix /tmp/vm-boot.log /tmp/menu-*.sh /tmp/layout-check.sh /tmp/validate-fixes.sh /tmp/menu-cmds.txt /tmp/shipped.txt)",
"Bash(rm -f /tmp/vm-verify-result)",
"Bash(kill -9 1285354)",
"Bash(sed -n '1,30p' features/scripts/battery-monitor.nix)",
"Bash(sed -n '40,75p' features/desktop/waybar/default.nix)",
"Bash(sed -n '140,160p' features/desktop/waybar/themes/summer-night/config.jsonc)",
"Bash(sed -n '10,20p' features/desktop/waybar/config/config.jsonc)",
"Bash(python3 -m json.tool)",
"Bash(/tmp/nomarchy-multidisk-test/run-vm.sh)",
"Bash(socat - UNIX-CONNECT:mon.sock)",
"Bash(/tmp/nomarchy-multidisk-test/boot-from-disk.sh)",
"Bash(socat - UNIX-CONNECT:bootmon.sock)",
"Bash(convert /tmp/nomarchy-multidisk-test/screen.ppm /tmp/nomarchy-multidisk-test/screen.png)",
"Bash(magick /tmp/nomarchy-multidisk-test/screen.ppm /tmp/nomarchy-multidisk-test/screen.png)",
"Bash(shellcheck --severity=error installer/install.sh)",
"Bash(curl -s \"https://api.github.com/repos/nix-community/home-manager/contents/modules/programs/vscode?ref=0d02ec1d0a05f88ef9e74b516842900c41f0f2fe\")",
"Bash(python3 -c \"import json,sys;[print\\(x['name']\\) for x in json.load\\(sys.stdin\\)]\")",
"Bash(curl -s \"https://api.github.com/repos/nix-community/home-manager/contents/modules/programs/vscode?ref=release-25.11\")",
"Bash(curl -s \"https://api.github.com/repos/nix-community/home-manager/contents/modules/programs/vscode?ref=49ca96b2714c5931e17401eff87f3edd42d2b0f2\")",
"Bash(python3 -c \"import json,sys;d=json.load\\(sys.stdin\\);[print\\(x['name']\\) for x in d]\")",
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://raw.githubusercontent.com/nix-community/home-manager/49ca96b2714c5931e17401eff87f3edd42d2b0f2/modules/programs/vscode/each-config.nix\")",
"Read(//nix/store/**)",
"Bash(mkdir -p __TRACKED_VAR__/nomarchy-install-test)",
"Bash(qemu-img create *)",
"Read(//home/bernardo/.config/nomarchy/**)",
"Read(//home/bernardo/.local/share/nomarchy/**)",
"Read(//home/bernardo/.config/elephant/**)",
"Read(//home/bernardo/.local/share/elephant/**)",
"Read(//home/bernardo/.config/walker/**)",
"WebFetch(domain:raw.githubusercontent.com)",
"Bash(python3 -c ' *)",
"Bash(nix-store -r /nix/store/hydnry53i2qbmqq0b0872wlbzdkpq1bd-source)",
"Bash(echo \"EXIT=$?\")",
"Bash(sed -n '44,50p' features/default.nix)",
"Bash(sed -n '14,20p' features/scripts/default.nix)",
"Bash(sed -n '11,17p' themes/engine/scripts.nix)",
"WebSearch",
"WebFetch(domain:github.com)",
"Bash(curl -sL \"https://api.github.com/repos/hyprwm/Hyprland/commits?sha=v0.55.3&per_page=100\")",
"Bash(curl -sL -o /tmp/hypr-fence-fix.patch \"https://github.com/hyprwm/Hyprland/commit/bdf0aaa5409fe2e4715e2796d669e16776de0233.patch\")",
"Bash(rm -f /tmp/nomarchy-2605.qcow2 /tmp/nomarchy-mon.sock /tmp/nomarchy-serial.sock)",
"Bash(NIX_DISK_IMAGE=/tmp/nomarchy-2605.qcow2 QEMU_OPTS=\"-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -serial unix:/tmp/nomarchy-serial.sock,server,nowait\" /tmp/nomarchy-vm-2605/bin/run-nomarchy-vm)",
"Bash(python3 *)",
"Bash(curl -sL https://raw.githubusercontent.com/hyprwm/Hyprland/v0.55.3/CMakeLists.txt)",
"Bash(curl -sL https://raw.githubusercontent.com/hyprwm/Hyprland/v0.55.2/CMakeLists.txt)",
"Bash(NIX_DISK_IMAGE=/tmp/nomarchy-2605.qcow2 QEMU_OPTS=\"-display none -monitor unix:/tmp/nomarchy-mon.sock,server,nowait -serial unix:/tmp/nomarchy-serial.sock,server,nowait\" /tmp/nomarchy-vm-2605-fixed/bin/run-nomarchy-vm)",
"Bash(xargs du -sh)",
"Bash(xargs du -csh)",
"Bash(curl -s https://raw.githubusercontent.com/nix-community/stylix/release-26.05/flake.nix)",
"Bash(nix build *)",
"Bash(echo \"exit $?\")",
"Bash(pkill -f \"test-install.sh\")",
"Bash(pkill -f \"qemu-system-x86_64.*NOMARCHY\\\\|qemu-system-x86_64.*nomarchy\")",
"Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-night --no-switch)",
"Bash(NOMARCHY_PATH=__TRACKED_VAR__ python3 pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py --quiet apply summer-day --no-switch)",
"Bash(xargs cat)",
"Bash(nix-instantiate --parse modules/home/rofi.nix)",
"Bash(nix-instantiate --parse modules/home/shell.nix)",
"Bash(command -v nix)",
"Bash(nix-instantiate --parse modules/home/keys.nix)",
"Bash(cp /home/bernardo/Projects/Nomarchy/modules/nixos/plymouth/logo.png /tmp/logo-orig.png)",
"Bash(nix run *)",
"Bash(cp /tmp/logo-text.png /home/bernardo/Projects/Nomarchy/.tmp-logo-preview.png)",
"Bash(cp /tmp/logo-text.png /home/bernardo/Projects/Nomarchy/preview-logo.png)",
"Bash(nix-instantiate --parse modules/nixos/plymouth.nix)",
"Bash(echo \"parse OK\")",
"Bash(echo \"=== build the plymouth theme derivation directly ===\")",
"Bash(nix-instantiate --parse modules/nixos/default.nix)",
"Bash(cp /nix/store/f18ij42lmpg07fn1m29aplayakx7kqpz-splash-test.png/* preview-splash-built.png)",
"Bash(cp /nix/store/f18ij42lmpg07fn1m29aplayakx7kqpz-splash-test.png preview-splash-built.png)",
"Bash(nix-instantiate --parse hosts/live.nix)",
"Bash(nix-instantiate --parse modules/home/stylix.nix)",
"Bash(nix-shell -p fontconfig --run \"fc-scan --format '%{family}\\\\n' modules/nixos/branding/Nomarchy.ttf\")"
]
}
}

3
.gitignore vendored
View File

@@ -8,3 +8,6 @@ result-*
__pycache__/ __pycache__/
.DS_Store .DS_Store
# Claude Code machine-local settings (permissions etc.)
.claude/settings.local.json

View File

@@ -186,8 +186,11 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|---|---|---| |---|---|---|
| `nomarchy.stateFile` | — (required) | Path to your theme-state.json | | `nomarchy.stateFile` | — (required) | Path to your theme-state.json |
| `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` | | `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` |
| `nomarchy.keyboard.layout` | `"us"` | XKB layout for the Hyprland session (installer writes it; pairs with xkb + `console.useXkbConfig` in system.nix) | | `nomarchy.keyboard.layout` | `"us"` | XKB layout for the Hyprland session (installer writes the matching `services.xserver.xkb` in system.nix; the console + LUKS prompt follow via the distro default) |
| `nomarchy.keyboard.variant` | `""` | XKB variant for the session | | `nomarchy.keyboard.variant` | `""` | XKB variant for the session |
| `nomarchy.keyboard.devices` | `{}` | Per-device layout overrides (Hyprland `device` blocks keyed by `hyprctl devices` name) — e.g. an external keyboard with its own layout/variant |
| `nomarchy.keyboard.layouts` | `[]` | Extra candidate layouts; when set, a watcher prompts (rofi) for a layout on a newly-connected keyboard and remembers it per-device |
| `nomarchy.nightlight.enable` | `false` | Scheduled blue-light filter (hyprsunset) — warm at night (`.temperature`, default 4000K) between `.sunset`/`.sunrise`, no shift by day |
| `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config | | `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config |
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar | | `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
| `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher | | `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher |
@@ -201,6 +204,8 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
| `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming | | `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming |
| `nomarchy.fastfetch.enable` | `true` | fastfetch fronted by the themed Nomarchy logo | | `nomarchy.fastfetch.enable` | `true` | fastfetch fronted by the themed Nomarchy logo |
| `nomarchy.keys.enable` | `true` | gpg-agent fronting SSH + pinentry-qt | | `nomarchy.keys.enable` | `true` | gpg-agent fronting SSH + pinentry-qt |
| `nomarchy.displays.enable` | `true` | nwg-displays interactive monitor arranger (helper for `nomarchy.monitors`) |
| `nomarchy.monitors` | `[]` | Declarative per-output layout → Hyprland `monitor` rules (applied on hotplug); `,preferred,auto,1` wildcard kept as fallback |
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed | | `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) | | `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) | | `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
@@ -214,6 +219,23 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
| `nomarchy.system.power.laptop` | `false` | Marks a laptop, gating battery-only features; the installer sets it when a battery is present | | `nomarchy.system.power.laptop` | `false` | Marks a laptop, gating battery-only features; the installer sets it when a battery is present |
| `nomarchy.system.power.thermal.enable` | `false` | thermald (Intel-only); the installer enables it on a GenuineIntel CPU | | `nomarchy.system.power.thermal.enable` | `false` | thermald (Intel-only); the installer enables it on a GenuineIntel CPU |
| `nomarchy.system.power.batteryChargeLimit` | `null` | Stop charging at this % (e.g. `80`) where the hardware supports it; needs `power.laptop` | | `nomarchy.system.power.batteryChargeLimit` | `null` | Stop charging at this % (e.g. `80`) where the hardware supports it; needs `power.laptop` |
| `nomarchy.services.tailscale.enable` | `false` | Opt-in: Tailscale mesh VPN (then `sudo tailscale up`) |
| `nomarchy.services.syncthing.enable` | `false` | Opt-in: Syncthing file sync as the login user (GUI at `127.0.0.1:8384`) |
| `nomarchy.services.podman.enable` | `false` | Opt-in: rootless Podman (`docker` aliased to it) |
| `nomarchy.services.flatpak.enable` | `false` | Opt-in: Flatpak + the Flathub remote |
| `nomarchy.services.pika.enable` | `false` | Opt-in: Pika Backup (GUI Borg backups) |
| `nomarchy.services.steam.enable` | `false` | Opt-in: Steam via `programs.steam` (32-bit libs, controller udev, Remote-Play ports) |
| `nomarchy.services.libvirt.enable` | `false` | Opt-in: libvirt/KVM + virt-manager GUI (login user added to `libvirtd`) |
| `nomarchy.services.obs.enable` | `false` | Opt-in: OBS Studio + a v4l2loopback virtual camera (selectable as a webcam in Zoom/Teams) |
| `nomarchy.services.docker.enable` | `false` | Opt-in: rootful Docker (user in `docker` group); don't enable alongside podman |
| `nomarchy.services.kdeconnect.enable` | `false` | Opt-in: KDE Connect phone integration (opens its firewall ports) |
| `nomarchy.services.gamemode.enable` | `false` | Opt-in: Feral GameMode performance daemon (`gamemoderun`) |
| `nomarchy.services.adb.enable` | `false` | Opt-in: Android adb/fastboot tools (systemd handles device udev rules) |
| `nomarchy.services.wireshark.enable` | `false` | Opt-in: Wireshark Qt GUI (user in `wireshark` group, capture without root) |
| `nomarchy.services.ollama.enable` | `false` | Opt-in: Ollama local LLM runtime on `127.0.0.1:11434` (CPU; GPU via `services.ollama.acceleration`) |
| `nomarchy.services.printing.enable` | `false` | Opt-in: CUPS + Avahi/mDNS network printer discovery |
| `nomarchy.services.openrgb.enable` | `false` | Opt-in: OpenRGB daemon for RGB peripheral/motherboard lighting |
| `nomarchy.services.restic.enable` | `false` | Opt-in: scheduled daily restic backup (set `.repository` + `.passwordFile`; 7/4/6 retention; list/restore via the `restic-nomarchy` wrapper) |
Beyond the `nomarchy.*` surface, the system layer turns on the usual Beyond the `nomarchy.*` surface, the system layer turns on the usual
desktop services with `lib.mkDefault` (override natively). One worth desktop services with `lib.mkDefault` (override natively). One worth
@@ -286,6 +308,32 @@ picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+X` power menu ·
clipboard history · `SUPER+Q` clipboard history · `SUPER+Q`
close · `SUPER+1..9` workspaces · `Print` region screenshot. close · `SUPER+1..9` workspaces · `Print` region screenshot.
Shell aliases (zsh, gated on `nomarchy.shell.enable`) — `alias` lists them
all; the curated set:
```sh
# navigation
.. ... .... # cd up 1 / 2 / 3 levels
# git
g gst # git · git status -sb
ga gaa # git add · git add -A
gc gcm # git commit · git commit -m
gco gsw gb # checkout · switch · branch
gd gds # diff · diff --staged
gl glg # log graph (last 20 · all branches)
gp gpl gf # push · pull · fetch --all --prune
# nix (system/home rebuilds keep their full sys-update / home-update names)
ns nr # nix shell · nix run (e.g. ns nixpkgs#ripgrep)
nfu nfc # nix flake update · check
nsearch ngc # nix search nixpkgs · nix-collect-garbage -d
# misc
path # print $PATH, one entry per line
reload # exec zsh (reload the shell)
```
## 6. Extending ## 6. Extending
- **New theme:** drop a JSON into `themes/` (schema = any existing preset), - **New theme:** drop a JSON into `themes/` (schema = any existing preset),
@@ -294,6 +342,18 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot.
the Nix modules. One place — there is no second renderer to keep in sync. the Nix modules. One place — there is no second renderer to keep in sync.
- **Importing more old-distro palettes:** - **Importing more old-distro palettes:**
`tools/import-palettes.py <palettes-dir> themes/`. `tools/import-palettes.py <palettes-dir> themes/`.
- **New opt-in feature (convention):** when a feature is off by default and
needs the user to set `nomarchy.*` options (e.g. night light, per-device
keyboard layouts, monitor layout, power management), ship a **commented**
example of it in `templates/downstream/home.nix` or `system.nix`. New
installs then enable it by uncommenting + tweaking, rather than hunting the
docs for the option name.
- **Applications:** the starter complete-workstation set (LibreOffice, VS
Code, GIMP, Inkscape) lives in the `home.packages` list in your
`home.nix` — it's a plain package list, so curate it directly: delete a
line to slim the machine, uncomment an extra (a browser, email, full TeX
Live), or add your own. No `nomarchy.apps.*` toggles — a package list is
already its own opt-out, so the distro doesn't impose these or wrap them.
## Roadmap & known issues ## Roadmap & known issues

View File

@@ -37,6 +37,20 @@ how to override it. Items marked ✓ are shipped.
launcher, no second theming pipeline; the dispatcher owns the menu launcher, no second theming pipeline; the dispatcher owns the menu
structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0
once mainline gained native Wayland, for its richer theming) once mainline gained native Wayland, for its richer theming)
-**Organize the picker into category submenus:** the root picker had grown
into a long flat list of 16 entries. It's now six: **Apps · Theme · Tools
· System · Power · Keybindings**. `Tools` (Calculator, Clipboard, Emoji,
Files, Web, Capture, Ask) and `System` (Network, Bluetooth, DND, and the
self-gated Snapshots / Power-profile) are submenus the dispatcher routes via
`nomarchy-menu tools|system`, each ending in a `← Back` entry that re-opens
the root. The direct `SUPER+CTRL+<mnemonic>` binds still hit the leaves
straight (bypassing the menu), and the self-gated entries still hide when
unavailable. Remaining (optional): a **Look & Feel** category once there are
more appearance toggles to group with Theme (night-light, wallpaper).
**General pattern (ongoing):** the grouping is a standing convention, not a
one-off — any new feature that earns a menu entry must be placed in the
right submenu (don't let the root creep back to a flat list), with its
direct `SUPER+CTRL+<mnemonic>` bind and self-gating as applicable.
-**Menu modules from rofi plugins:** the old `calc` flow committed the -**Menu modules from rofi plugins:** the old `calc` flow committed the
expression blind (result only in the *next* menu's `-mesg`) and `qalc -t` expression blind (result only in the *next* menu's `-mesg`) and `qalc -t`
misparsed common phrasings (`15% of 200``rem(15, 1 B)`, the natural- misparsed common phrasings (`15% of 200``rem(15, 1 B)`, the natural-
@@ -99,10 +113,15 @@ how to override it. Items marked ✓ are shipped.
summer-day/night menu buttons now use its `U+F000` glyph with summer-day/night menu buttons now use its `U+F000` glyph with
`font-family: Nomarchy` pinned in their CSS (Nerd Fonts also occupy `font-family: Nomarchy` pinned in their CSS (Nerd Fonts also occupy
U+F000, so the pin is required). The other themes have no logo button. U+F000, so the pin is required). The other themes have no logo button.
- **Quality-of-life command aliases:** assemble a curated collection of - **Quality-of-life command aliases:** a curated set ships on by default in
shell aliases/abbreviations for common operations (git, nix, navigation, `modules/home/shell.nix` — navigation (`..`/`...`/`....`), a git block
the nomarchy helpers, …), themed into the zsh shell experience (`g`, `gst`, `ga`/`gaa`, `gc`/`gcm`, `gco`/`gsw`, `gb`, `gd`/`gds`,
(`modules/home/shell.nix`). Decide scope and which to ship on by default. `gl`/`glg`, `gp`/`gpl`, `gf`), and a nix block (`ns`, `nr`, `nfu`, `nfc`,
`nsearch`, `ngc`), plus `path` and `reload`. Scope decision: short where it
helps but **never shadow a real binary** (same rule as rg/fd — the git block
deliberately avoids `gs`/ghostscript); system/home rebuilds keep their full
`sys-update`/`home-update` names rather than getting cryptic aliases. The set
is documented in README §5 (Day-to-day), and `alias` lists them in-shell.
- **Theme-switch feedback:** ✓ the "rebuilding…" notification is now - **Theme-switch feedback:** ✓ the "rebuilding…" notification is now
persistent (timeout 0) and replaced in place by "applied ✓" / failure persistent (timeout 0) and replaced in place by "applied ✓" / failure
via a synchronous tag, so a multi-minute switch never reads as a failed via a synchronous tag, so a multi-minute switch never reads as a failed
@@ -119,11 +138,21 @@ how to override it. Items marked ✓ are shipped.
tracks the palette), `ls`→eza, `cd`→zoxide, plus `lt`/`tree`. tracks the palette), `ls`→eza, `cd`→zoxide, plus `lt`/`tree`.
`nomarchy.shell.enable`. (Deliberately did NOT alias grep→rg / find→fd: `nomarchy.shell.enable`. (Deliberately did NOT alias grep→rg / find→fd:
their flags differ enough to surprise; `rg`/`fd` ship as themselves.) their flags differ enough to surprise; `rg`/`fd` ship as themselves.)
- **Default application suite:** install a complete-workstation set - **Default application suite:** a starter complete-workstation set
(vscode, libreoffice, gimp, inkscape, texlive-full, …) behind a `libreoffice-fresh`, `vscode`, `gimp`, `inkscape` — ships *active* in the
`nomarchy.apps.*` option surface so each is individually opt-out for downstream `templates/downstream/home.nix` `home.packages`, with the heavier
users who want a leaner machine. Watch closure size (texlive-full is opt-ins (`texliveFull` — multi-GB; `texliveMedium` is lighter — plus
multi-GB — likely default-off or a lighter scheme). browser/email) commented just below. **Decision: no `nomarchy.apps.*` option
surface.** Unlike `nomarchy.services.*` (real config behind each toggle —
systemd units, subuid ranges, the Flathub oneshot), a bare package install
has nothing behind the toggle: `mkIf x { environment.systemPackages = [p]; }`
just reinholds what a package list already gives, and the honest opt-out for
a package is *deleting the line*. So the suite is the user's own curated list
(extending the existing `# firefox` convention), not a distro-module feature —
which also keeps the apps out of the live ISO automatically (it never starts
from the template) and respects that editor/office/graphics are personal-taste
choices, not defaults to impose. Unfree `vscode` evaluates fine — `mkFlake`
and the system both set `allowUnfree = true`.
-**Plymouth logo contrast:** the shipped art was a fixed navy that -**Plymouth logo contrast:** the shipped art was a fixed navy that
vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every
element from the palette at build time (flat fill, alpha kept): element from the palette at build time (flat fill, alpha kept):
@@ -131,13 +160,15 @@ how to override it. Items marked ✓ are shipped.
accent. Reads on light and dark; follows the theme as of the last system accent. Reads on light and dark; follows the theme as of the last system
rebuild (like the background tint). rebuild (like the background tint).
-**Hibernate double-unlock:** on resume from hibernate the LUKS -**Hibernate double-unlock:** on resume from hibernate the LUKS
passphrase already gates the machine, but hypridle's `before_sleep_cmd` passphrase already gates the machine, but locking hyprlock before sleep
also locked hyprlock, so the user typed a password twice. Fixed with a meant a second password. Fixed by *not locking* before an encrypted
`nomarchy-hibernate-unlock` systemd unit (`modules/nixos/default.nix`) hibernate: a `nomarchy-lock-before-sleep` systemd unit
that dismisses hyprlock *after* a hibernate resume (`WantedBy (`modules/nixos/default.nix`) takes over from hypridle's `before_sleep_cmd`
hibernate.target`, `After systemd-hibernate.service` → runs post-resume), and locks on the RAM-resume sleeps (suspend / hybrid-sleep / suspend-then-
gated on the disk being LUKS-encrypted. Suspend (and the RAM-resume phase hibernate) always, but skips `hibernate.target` when the disk is LUKS-
of suspend-then-hibernate) keep locking — they have no passphrase gate. encrypted. (A first attempt that dismissed hyprlock *after* resume was
wrong — killing a Wayland session-lock client trips its "go to a tty"
crash failsafe instead of unlocking, which is the error screen it caused.)
-**Key agents & pinentry:** ships `modules/home/keys.nix` -**Key agents & pinentry:** ships `modules/home/keys.nix`
(`nomarchy.keys.enable`): one agent — `services.gpg-agent` with (`nomarchy.keys.enable`): one agent — `services.gpg-agent` with
`enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland, `enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland,
@@ -148,14 +179,31 @@ how to override it. Items marked ✓ are shipped.
cache. Remaining (optional): a session-level `SSH_AUTH_SOCK` export so GUI cache. Remaining (optional): a session-level `SSH_AUTH_SOCK` export so GUI
clients launched outside a shell also see the agent. clients launched outside a shell also see the agent.
- **Sanitize & organize the repo:** a housekeeping pass for consistency - **Sanitize & organize the repo:** a housekeeping pass for consistency
and clarity. Candidate targets (scope still to be defined): prune and clarity.
now-redundant config (e.g. the installer still writes - ✓ pruned now-redundant config: the installer no longer writes
`console.useXkbConfig` / `boot.initrd.systemd.enable` into `system.nix`, `console.useXkbConfig` / `boot.initrd.systemd.enable` into `system.nix`
which are distro-wide defaults as of the LUKS-keymap fix); reconcile the (distro-wide defaults since the LUKS-keymap fix), nor the offline-pin
README option tables with the actual `nomarchy.*` surface; audit test fixture in `flake.nix`.
comments/docs for drift against the code; and re-check that every file - ✓ reconciled the README option tables with the live `nomarchy.*`
still earns its place under the `modules`/`hosts`/`themes`/`pkgs`/`tools` surface (added keys/fastfetch/snapper/greeter.autoLogin).
rule of thumb. - ✓ untracked `.claude/settings.local.json` (machine-local) + gitignored.
-**branch/release model:** `main` is the development default; `v1` is
the release pointer downstreams pin (`?ref=v1`), advanced to main once
a batch is tested — rolling, no tags. Brought `main` current (it was a
stale legacy-merge), and deleted the dead `refactor` / `wave/nixos-26.05`
branches (kept `legacy` for history). See the comment in `flake.nix`.
- ✓ re-checked that every file earns its place under the
`modules`/`hosts`/`themes`/`pkgs`/`tools` rule of thumb: no orphaned
modules (`keybinds.nix` is a *data* module `import`-ed by hyprland/rofi,
so correctly absent from `default.nix`), no dead-code markers, no
editor/backup cruft, each `themes/<slug>.json` palette pairs with its
optional `<slug>/` overrides dir, and `hosts/{default,live}` are the
documented reference + ISO hosts. Structure is clean as-is.
- Open decision (deferred): the repo declares **no formatter** (no flake
`formatter`, no `.editorconfig`); `.nix` files use deliberate aligned
hand-formatting, so `nixfmt-rfc-style --check` flags ~33 files. Adopting a
formatter would be a one-time repo-wide reformat that flattens that
alignment — a maintainer call, not a silent cleanup, so left untouched.
- **Full docs review & restructure:** a dedicated pass over all the prose, - **Full docs review & restructure:** a dedicated pass over all the prose,
not just the code-adjacent cleanup the repo-sanitize item covers. The not just the code-adjacent cleanup the repo-sanitize item covers. The
README has grown to ~540 lines with a ~200-line roadmap inline — the README has grown to ~540 lines with a ~200-line roadmap inline — the
@@ -195,47 +243,178 @@ how to override it. Items marked ✓ are shipped.
idle behaviour. idle behaviour.
- Remaining: a boot-only→event-driven charge-limit re-apply (udev) if a - Remaining: a boot-only→event-driven charge-limit re-apply (udev) if a
firmware resets the threshold on unplug. firmware resets the threshold on unplug.
-**Waybar parity:** the `custom/powerprofile` indicator now shows in the
summer-day/night whole-swap themes too. `powerProfileStatus`/`Cycle` are
named `writeShellScriptBin`s on PATH (`waybar.nix` home.packages), so the
generated config and the static `waybar.jsonc` both exec them by bare
name (the same parity the DND bell has). General pattern: any new
generated-config module needs adding to the whole-swap themes too.
- **Full hardware enablement (beyond nixos-hardware):** nixos-hardware
profiles cover *basic* per-model functionality (quirks, drivers), but the
*advanced* features of a machine — keyed more on the CPU/GPU generation
than the model — are left for the user to wire up by hand. Close that gap
without making it finnicky: extend the installer's existing autodetection
(DMI → nixos-hardware profile today; add CPU vendor/arch, GPU, NPU,
fingerprint reader, SSD) to switch on the *safe, broadly-beneficial*
features automatically, and expose a small `nomarchy.hardware.*` surface
(per-vendor where it helps, e.g. `nomarchy.hardware.amd.*`) for the heavy
or experimental bits. Goal: full utilisation out of the box — opt-*out* for
the safe defaults, opt-*in* for the multi-GB / experimental extras.
Complements nixos-hardware (model quirks) rather than replacing it; keep
the detection in the installer's `hardware-db` so an installed machine
bakes the right defaults, all overridable through `nomarchy.hardware.*`.
Worked example — ThinkPad T14s (Ryzen 7 PRO 7840U + Radeon 780M):
- **GPU compute — ROCm:** the 780M is an RDNA3 iGPU (gfx1103) ROCm doesn't
officially list, so it needs `HSA_OVERRIDE_GFX_VERSION=11.0.0`. Multi-GB
closure → opt-in (`nomarchy.hardware.amd.rocm.enable`); unlocks GPU
PyTorch / Ollama.
- **Ryzen AI NPU (XDNA):** the `amdxdna` kernel driver (mainline 6.14+) +
the XRT/xdna userspace runtime + firmware. Niche/experimental → opt-in.
- **AMD P-State EPP (Zen 4 power):** `amd_pstate=active` for the EPP
governor, which power-profiles-daemon (already shipped, `power.nix`)
drives per profile. Broadly beneficial on Zen 2+ → default on when the
installer sees an AMD CPU.
- **Hardware video acceleration (VA-API):** mesa's radeonsi exposes VA-API
on AMD (`LIBVA_DRIVER_NAME=radeonsi`, libva + drivers in
`hardware.graphics.extraPackages`); Intel uses `intel-media-driver`.
Broadly beneficial → default on, vendor-detected.
- **LVFS firmware & biometrics:** fwupd already ships (✓, `services.fwupd`).
Add fingerprint: `services.fprintd` when a reader is detected, with
opt-in PAM integration for login/sudo (password-only stays the default
for the cautious).
- **SSD periodic TRIM:** `services.fstrim.enable` (weekly) — safe and
beneficial, so default on (trivial enough it may warrant being
distro-wide regardless).
Sub-items here can graduate into their own roadmap entries as they're
scoped; the unifying work is the detection + `nomarchy.hardware.*` surface.
- **Opt-in services & integrations:** the counterpart to the opt-*out* - **Opt-in services & integrations:** the counterpart to the opt-*out*
application suite above — heavier or more personal integrations shipped application suite above — heavier or more personal integrations shipped
**off by default**, each a `nomarchy.services.<name>.enable` toggle a **off by default**, each a `nomarchy.services.<name>.enable` toggle a
downstream flips on in one line. Keeps the base lean while making common downstream flips on in one line. Keeps the base lean while making common
additions trivial. Candidates by area: additions trivial. **✓ Seventeen shipped** in `modules/nixos/services.nix`
- **cloud/sync:** Nextcloud client; Syncthing (`services.syncthing`) (system-side), each with a commented example in the downstream `system.nix`
- **local AI:** LM Studio (`lmstudio`); Ollama (`services.ollama`, optional template: `tailscale`, `syncthing` (runs as the login user, GUI on
GPU accel) — pairs with the menu's Ask-Claude philosophy 127.0.0.1:8384), `podman` (rootless, `docker` aliased, user subuid/subgid),
- **containers/VMs:** Docker/Podman; libvirt + virt-manager `flatpak` (+ Flathub remote added by a oneshot), `pika` (Pika Backup GUI),
- **networking:** Tailscale (`services.tailscale`); WireGuard `steam` (`programs.steam` — the 32-bit stack, controller udev, Remote-Play
- **gaming/media:** Steam (`programs.steam`); OBS Studio ports a bare package can't), `libvirt` (libvirtd + virt-manager, user added
- **devices:** printing (CUPS + Avahi); KDE Connect / phone integration; to the `libvirtd` group), `obs` (OBS Studio + a v4l2loopback virtual camera
OpenRGB usable as a webcam in Zoom/Teams), `docker` (rootful; asserts against the
- **backup:** restic/borg (`services.restic`) podman docker-compat), `kdeconnect`, `gamemode`, `adb` (android-tools;
- **escape hatch:** Flatpak (`services.flatpak`) for apps outside nixpkgs systemd handles the udev rules), `wireshark` (Qt GUI + the `wireshark`
group), `ollama` (local LLM API on 127.0.0.1:11434), `printing` (CUPS +
Avahi mDNS), `openrgb` (RGB lighting daemon), and `restic` (scheduled
daily backup — a small option surface: `repository`/`passwordFile`/`paths`,
7/4/6 retention). More candidates by area:
-**cloud/sync:** Syncthing (`services.syncthing`); Nextcloud client is a
bare tray app (in the app-suite menu, not a service)
-**local AI:** Ollama (`nomarchy.services.ollama`, optional GPU accel)
ships; LM Studio is a (commented) app-suite package (a bare GUI, no
service) — pairs with the menu's Ask-Claude philosophy
-**containers/VMs:** Podman (`virtualisation.podman`); libvirt +
virt-manager (`nomarchy.services.libvirt`); Docker (`nomarchy.services.docker`)
-**networking:** Tailscale (`services.tailscale`); WireGuard needs no
toggle — NetworkManager (on by default) imports `.conf` tunnels natively;
the `wireguard-tools` CLI is in the app-suite menu
-**gaming/media:** Steam (`nomarchy.services.steam`); OBS Studio
(`nomarchy.services.obs`, with the v4l2loopback virtual camera); GameMode
(`nomarchy.services.gamemode`)
-**devices:** printing (CUPS + Avahi, `nomarchy.services.printing`); KDE
Connect (`nomarchy.services.kdeconnect`); OpenRGB
(`nomarchy.services.openrgb`). Plus dev tooling — Android `adb` and
`wireshark` toggles
-**backup:** Pika Backup (`pika-backup`, GUI over Borg); scheduled
headless restic (`nomarchy.services.restic`)
-**escape hatch:** Flatpak (`services.flatpak`) + Flathub, for apps
outside nixpkgs
Decisions: the curated set; whether system services and GUI apps share one Surface decided: `nomarchy.services.*`, system-side for system services
surface or split (`nomarchy.services.*` system-side vs home-side packages); (home-side ones can extend the same prefix later). Remaining: curate the
and how it relates to `nomarchy.apps.*` (opt-out suite). Unfree entries are rest (Ollama, containers, Steam/OBS, printing, backups, Flatpak …). The
opt-out application *suite* is deliberately **not** a parallel
`nomarchy.apps.*` surface — it's a curated `home.packages` list in the
downstream template (see "Default application suite" above), since a bare
package install has no config to put behind a toggle. Unfree entries are
already covered (`allowUnfree = true`). already covered (`allowUnfree = true`).
- **Display / monitor management:** today Hyprland just uses - **Display / monitor management:** declarative `nomarchy.monitors` (a
`monitor = ,preferred,auto,1` (mkDefault). Add a real external-monitor/dock per-output schema — name/resolution/position/scale/transform/mirror/
story: declarative per-output config, hotplug profiles (kanshi-style, or bitdepth/vrr/disable) generates Hyprland `monitor` rules, keeping the
Hyprland's own monitor rules), and optionally a GUI arranger (nwg-displays `,preferred,auto,1` wildcard as the fallback; Hyprland applies them on
writes Hyprland monitor config). Decide declarative-only vs GUI, and the hotplug, so a declared external/dock output arranges itself on connect
option shape (`nomarchy.monitors` vs plain Hyprland `monitor` lists). (no kanshi — it fights Hyprland's own output management). `nwg-displays`
- **Night light / blue-light filter:** schedulable colour-temperature shift ships behind `nomarchy.displays.enable` as an interactive arranger (a
via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) helper to find values; the declarative config stays the source of truth
sunset/sunrise or a fixed schedule, with a menu + Waybar toggle. its output file isn't sourced). Remaining: true docked/undocked **profile
- **Runtime keyboard-layout switching:** the session layout is a single switching** of the *same* outputs (Hyprland's per-output rules cover the
`nomarchy.keyboard.layout` today. Support a list of layouts with a toggle common "external connects → arrange" case, not multi-layout toggles), and
bind (`hyprctl switchxkblayout` / xkb `grp:` options) and a Waybar optionally workspace-to-monitor binding.
indicator for the active layout. Natural follow-on to the LUKS-keymap work -**Night light / blue-light filter:** `nomarchy.nightlight` (opt-in) —
— keep the system (console/initrd) and session layouts in sync. a scheduled colour-temperature shift via `hyprsunset` (Hyprland-native),
- **Do-Not-Disturb:** a swaync DND toggle (`swaync-client -dn`) wired into warm at night, identity (no shift) by day. `modules/home/nightlight.nix`
the menu and a Waybar indicator, to silence notifications for drives the HM `services.hyprsunset` with two time-based profiles
presentations/focus. (`sunrise` → identity, `sunset``temperature`), so hyprsunset handles the
- **Snapshot browse/restore UX:** snapper already takes BTRFS timeline schedule and the on-login state. Needs an on-hardware check that hyprsunset
snapshots (`nomarchy.system.snapper`); surface them from the desktop — a applies the active profile at session start. Remaining (optional): a menu +
rofi menu (or btrfs-assistant) to browse/diff/restore and boot-from- Waybar toggle to force it on/off, and geo (lat/long) auto sunset/sunrise
snapshot — so rollback isn't CLI-only. (would mean wlsunset, which schedules by location).
- **Keyboard layouts (per-device + switching):**
-**Per-device declarative layout:** `nomarchy.keyboard.devices`
(`{ "<hyprctl-device-name>" = { layout; variant; }; }`) generates Hyprland
`device` blocks that override the session `nomarchy.keyboard.layout` for a
named keyboard — e.g. an external board that's physically a different
layout than the laptop's. Hyprland applies it whenever that device
connects (the "external keyboard remembers its layout" case, done the
declarative way). A Waybar `hyprland/language` indicator shows the active
layout, gated on more than one layout being in play (comma in the session
layout, or any per-device override) so single-layout bars stay clean.
-**Interactive new-keyboard prompt (runtime + persist):** 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 in `keyboard.devices` and
hasn't been chosen before, it pops a rofi layout picker, applies the
choice with `hyprctl switchxkblayout` (an index into the candidate set,
which `input.kb_layout` carries), and remembers it per-device in
`~/.local/state/nomarchy/keyboard-layouts` — re-applied silently on later
reconnects. The boot-time set (incl. the built-in keyboard) is never
prompted. The runtime-remember complement to the declarative
`keyboard.devices`; a stateful runtime piece by design. **Pending an
on-hardware test** (hotplug isn't verifiable in CI). Remaining (bonus):
offer to write a choice into `keyboard.devices` so it graduates to the
reproducible config.
- Remaining: a multi-layout cycle bind (`hyprctl switchxkblayout` / xkb
`grp:` options) for switching layouts on one keyboard; add the
`hyprland/language` module to the summer whole-swap themes for parity
(the gating doesn't translate to their static JSON). Keep the system
(console/initrd) and session layouts in sync (the LUKS-keymap work).
-**Do-Not-Disturb:** swaync DND toggle wired into the menu
(`nomarchy-menu dnd`, in the picker, SUPER+CTRL+D) and a Waybar bell
indicator (`custom/notification` via `swaync-client -swb`: shows the
notification count + DND state, left-click toggles the panel,
right-click toggles DND). Turning DND off confirms with a toast; turning
it on is silent (notifications are suppressed — the bell-off glyph is the
cue).
-**Snapshot browse/restore UX:** `nomarchy-menu snapshot` (in the SUPER+M
picker, self-gated on snapper) launches **btrfs-assistant** — the
desktop browse/diff/restore/rollback UI, elevating via polkit (the safe
choice for a destructive, root-only op vs a fat-fingerable rofi rollback).
Shipped system-side gated on `nomarchy.system.snapper`. Also extended
snapper to snapshot **/home** by default (hourly 5 / daily 7 / weekly 4)
when it's its own BTRFS subvolume; a boot oneshot creates the required
`/home/.snapshots` subvolume if missing (covers existing installs, no disko
change). **The backend is VM-verified** (a full BTRFS+LUKS install: both
`root`+`home` configs, a `/home` timeline snapshot taken, `/home/.snapshots`
a real subvolume, the oneshot `Result=success`).
-**Known bug — btrfs-assistant 2.2 segfaults on launch** in nixpkgs
26.05: it crashes inside `libbtrfsutil.so.1.4.0` (a library ABI mismatch,
confirmed in the VM regardless of GL/Qt platform — so it's not a VM/GL
artifact and will crash on hardware too). The menu wiring is correct and
the app is installed, but the GUI doesn't open. The snapshot *backend*
(the actual snapshots) is unaffected. Fix path: a nixpkgs override aligning
its libbtrfsutil, or wait for an upstream bump; meanwhile the menu entry
is a no-op when the binary crashes. Fallback if it lingers: a rofi-based
snapshot menu (no btrfs-assistant dependency).
- Remaining (optional): a keyboard-driven rofi browse for quick glances;
boot-from-snapshot needs a systemd-boot equivalent of grub-btrfs.
- **Update awareness:** updates are manual today (`sys-update`/`home-update`); - **Update awareness:** updates are manual today (`sys-update`/`home-update`);
add a Waybar indicator / notification when flake inputs are stale or a new add a Waybar indicator / notification when flake inputs are stale or a new
nixpkgs rev is available (optionally with a pending-change count). Augments, nixpkgs rev is available (optionally with a pending-change count). Augments,
@@ -287,5 +466,8 @@ how to override it. Items marked ✓ are shipped.
dark. `stylix.nix` now sets `dconf` `org/gnome/desktop/interface dark. `stylix.nix` now sets `dconf` `org/gnome/desktop/interface
color-scheme` = `prefer-light`/`prefer-dark` from `t.mode` color-scheme` = `prefer-light`/`prefer-dark` from `t.mode`
(xdg-desktop-portal-gtk already ships, and `programs.dconf` is on (xdg-desktop-portal-gtk already ships, and `programs.dconf` is on
system-side), so libadwaita/Qt follow the palette. Needs a live session system-side), so libadwaita/Qt follow the palette. Confirmed in a live
to confirm the portal picks it up for all apps. session (headless QEMU): the portal's `org.freedesktop.appearance`
`color-scheme` reads `1` on a dark theme and flips to `2` after switching
to a light one (catppuccin-latte) — so xdg-desktop-portal-gtk does relay
the dconf value GTK4/Qt apps read.

View File

@@ -51,8 +51,11 @@
(nixpkgs.lib.concatStringsSep "\n" (nixpkgs.lib.concatStringsSep "\n"
(builtins.attrNames nixos-hardware.nixosModules)); (builtins.attrNames nixos-hardware.nixosModules));
nixpkgsPath = nixpkgs.outPath; nixpkgsPath = nixpkgs.outPath;
# v1 is the release branch: installed machines track it on # Branch model: `main` is the development default; `v1` is the
# `nix flake update` instead of the forge's default branch. # release pointer — the stable line for the NixOS 26.05 rewrite,
# advanced to main once a batch is tested (rolling, no tags). A
# future major bump would become `v2`. Installed machines track
# `?ref=v1` on `nix flake update`, not the forge's default branch.
flakeUrl = "git+${gitUrl}?ref=v1"; flakeUrl = "git+${gitUrl}?ref=v1";
# Future updates re-resolve from the forge (original); the install # Future updates re-resolve from the forge (original); the install
# itself resolves from the ISO store (locked = path) — fully # itself resolves from the ISO store (locked = path) — fully
@@ -252,12 +255,6 @@
({ lib, ... }: { ({ lib, ... }: {
nomarchy.system.snapper.enable = true; nomarchy.system.snapper.enable = true;
nomarchy.system.greeter.autoLogin = username; nomarchy.system.greeter.autoLogin = username;
# The installer writes these (keyboard-everywhere
# incl. the LUKS prompt) — mirror them or the
# initrd/vconsole pins don't match an offline
# install's rebuild set.
console.useXkbConfig = true;
boot.initrd.systemd.enable = true;
# nixos-generate-config keys microcode off # nixos-generate-config keys microcode off
# enableRedistributableFirmware (now on): the # enableRedistributableFirmware (now on): the
# real machine enables exactly one vendor — # real machine enables exactly one vendor —

View File

@@ -17,6 +17,7 @@
./idle.nix # hyprlock + hypridle, themed from the same JSON ./idle.nix # hyprlock + hypridle, themed from the same JSON
./yazi.nix # flagship TUI file manager, themed + plugins ./yazi.nix # flagship TUI file manager, themed + plugins
./osd.nix # swayosd volume/brightness OSD, themed ./osd.nix # swayosd volume/brightness OSD, themed
./nightlight.nix # scheduled blue-light filter (hyprsunset), opt-in
./shell.nix # zsh + starship + bat/eza/zoxide, themed ./shell.nix # zsh + starship + bat/eza/zoxide, themed
./keys.nix # gpg-agent (fronts SSH) + pinentry-qt ./keys.nix # gpg-agent (fronts SSH) + pinentry-qt
./fastfetch.nix # system info with the themed Nomarchy logo ./fastfetch.nix # system info with the themed Nomarchy logo

View File

@@ -41,6 +41,77 @@ let
# (rofi.nix renders the same list). Edit them in ./keybinds.nix. # (rofi.nix renders the same list). Edit them in ./keybinds.nix.
keybinds = import ./keybinds.nix; keybinds = import ./keybinds.nix;
mkBind = b: "${b.mods}, ${b.key}, ${b.action}"; mkBind = b: "${b.mods}, ${b.key}, ${b.action}";
# A nomarchy.monitors entry -> a Hyprland `monitor` rule. Unset optional
# fields are omitted; `resolution = "disable"` collapses to the short form.
monitorRule = m:
if m.resolution == "disable" then "${m.name}, disable"
else lib.concatStringsSep ", " (
[ m.name m.resolution (toString m.position) (toString m.scale) ]
++ lib.optionals (m.transform != null) [ "transform" (toString m.transform) ]
++ lib.optionals (m.mirror != null) [ "mirror" m.mirror ]
++ lib.optionals (m.bitdepth != null) [ "bitdepth" (toString m.bitdepth) ]
++ lib.optionals (m.vrr != null) [ "vrr" (toString m.vrr) ]
++ lib.optional (m.extra != "") m.extra
);
# Keyboard layout candidates: the primary nomarchy.keyboard.layout (split on
# commas) plus any nomarchy.keyboard.layouts. Drives input.kb_layout so all
# of them are switchable, and feeds the watcher's picker.
kbLayouts = lib.unique (
(lib.splitString "," config.nomarchy.keyboard.layout)
++ config.nomarchy.keyboard.layouts
);
kbAutoSwitch = config.nomarchy.keyboard.layouts != [ ];
# On a keyboard that connects after login (not declared in
# keyboard.devices, not already remembered), ask for a layout and persist it
# per-device, re-applying silently on later reconnects. Stateful runtime
# piece — the complement to the declarative keyboard.devices. Reliable
# primitives only: poll hyprctl devices, apply via switchxkblayout (an index
# into kbLayouts). NOTE: needs an on-hardware test (hotplug isn't verifiable
# in CI).
keyboardWatch = pkgs.writeShellScriptBin "nomarchy-keyboard-watch" ''
set -u
layouts="${lib.concatStringsSep " " kbLayouts}"
declared="${lib.concatStringsSep " " (builtins.attrNames config.nomarchy.keyboard.devices)}"
state="''${XDG_STATE_HOME:-$HOME/.local/state}/nomarchy/keyboard-layouts"
mkdir -p "$(dirname "$state")"; [ -f "$state" ] || : > "$state"
layout_index() { i=0; for l in $layouts; do [ "$l" = "$1" ] && { printf %s "$i"; return; }; i=$((i + 1)); done; printf %s -1; }
apply() { idx=$(layout_index "$2"); [ "$idx" -ge 0 ] && hyprctl switchxkblayout "$1" "$idx" >/dev/null 2>&1; }
keyboards() { hyprctl devices -j | jq -r '.keyboards[].name'; }
is_declared() { case " $declared " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
saved_for() { while IFS='=' read -r k v; do [ "$k" = "$1" ] && { printf '%s' "$v"; return; }; done < "$state"; }
# Startup: re-apply remembered layouts, never prompt so the built-in
# keyboard just stays on the session default.
prev=" "
for kb in $(keyboards); do
prev="$prev$kb "
is_declared "$kb" && continue
s=$(saved_for "$kb"); [ -n "$s" ] && apply "$kb" "$s"
done
# Watch for keyboards that connect later.
while :; do
sleep 3
cur=" "
for kb in $(keyboards); do
cur="$cur$kb "
case "$prev" in *" $kb "*) continue ;; esac
is_declared "$kb" && continue
s=$(saved_for "$kb")
if [ -n "$s" ]; then
apply "$kb" "$s"
else
choice=$(printf '%s\n' $layouts | rofi -dmenu -p "Layout · $kb")
[ -n "$choice" ] && { printf '%s=%s\n' "$kb" "$choice" >> "$state"; apply "$kb" "$choice"; }
fi
done
prev="$cur"
done
'';
in in
{ {
wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable { wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable {
@@ -59,9 +130,12 @@ in
"$mod" = "SUPER"; "$mod" = "SUPER";
"$terminal" = config.nomarchy.terminal; "$terminal" = config.nomarchy.terminal;
# mkDefault so a downstream `monitor = [...]` replaces it with a # `,preferred,auto,1` is the wildcard fallback for any output not in
# plain assignment (the live ISO uses mkForce for the same reason). # nomarchy.monitors; the generated rules are appended and win by name,
monitor = lib.mkDefault [ ",preferred,auto,1" ]; # and Hyprland applies them on hotplug. mkDefault so a downstream
# `monitor = [...]` replaces the lot (the live ISO uses mkForce too).
monitor = lib.mkDefault
([ ",preferred,auto,1" ] ++ map monitorRule config.nomarchy.monitors);
# exec-once is a list at normal priority, so downstream additions # exec-once is a list at normal priority, so downstream additions
# CONCATENATE (your autostart runs alongside ours) rather than # CONCATENATE (your autostart runs alongside ours) rather than
@@ -72,7 +146,7 @@ in
# Paint the wallpaper as soon as the session is up (waits for # Paint the wallpaper as soon as the session is up (waits for
# the daemon internally). # the daemon internally).
"nomarchy-theme-sync wallpaper" "nomarchy-theme-sync wallpaper"
]; ] ++ lib.optional kbAutoSwitch "${keyboardWatch}/bin/nomarchy-keyboard-watch";
# ── Theme-driven look ────────────────────────────────────────── # ── Theme-driven look ──────────────────────────────────────────
# These flow from theme-state.json and stay at NORMAL priority: # These flow from theme-state.json and stay at NORMAL priority:
@@ -128,12 +202,20 @@ in
input = { input = {
# kb_layout/variant come from nomarchy.keyboard.* — set that # kb_layout/variant come from nomarchy.keyboard.* — set that
# option (the installer writes it; it also drives tty/LUKS). # option (the installer writes it; it also drives tty/LUKS).
kb_layout = config.nomarchy.keyboard.layout; kb_layout = lib.concatStringsSep "," kbLayouts;
kb_variant = config.nomarchy.keyboard.variant; kb_variant = config.nomarchy.keyboard.variant;
follow_mouse = lib.mkDefault 1; follow_mouse = lib.mkDefault 1;
touchpad.natural_scroll = lib.mkDefault true; touchpad.natural_scroll = lib.mkDefault true;
}; };
# Per-device keyboard layouts (nomarchy.keyboard.devices): each
# overrides the session kb_layout for one named keyboard — e.g. an
# external board that's physically a different layout than the laptop's
# built-in one. Hyprland applies them whenever the device connects.
device = lib.mapAttrsToList
(name: d: { inherit name; kb_layout = d.layout; kb_variant = d.variant; })
config.nomarchy.keyboard.devices;
# dwindle:pseudotile was removed in Hyprland 0.55 (the `pseudo` # dwindle:pseudotile was removed in Hyprland 0.55 (the `pseudo`
# dispatcher still exists); setting it aborts config parsing. # dispatcher still exists); setting it aborts config parsing.
dwindle.preserve_split = lib.mkDefault true; dwindle.preserve_split = lib.mkDefault true;
@@ -183,4 +265,12 @@ in
]; ];
}; };
}; };
# nwg-displays: interactive monitor arranger (applies live via hyprctl and
# writes a config file). A helper to find values for nomarchy.monitors —
# the declarative config stays the source of truth (we don't source its
# output). Gated on its toggle; pointless without the Hyprland session.
home.packages = lib.optionals
(config.nomarchy.hyprland.enable && config.nomarchy.displays.enable)
[ pkgs.nwg-displays ];
} }

View File

@@ -64,11 +64,13 @@ in
settings = { settings = {
general = { general = {
lock_cmd = "pidof hyprlock || hyprlock"; lock_cmd = "pidof hyprlock || hyprlock";
# Locks before every sleep. For suspend that's exactly right; on an # No before_sleep_cmd here: locking before sleep is driven
# encrypted hibernate the LUKS resume already gates the machine, so # system-side so it can fire for suspend but skip an encrypted
# the system side dismisses this lock post-resume to avoid a double # hibernate — whose LUKS resume already gates the machine, so a
# unlock — see nomarchy-hibernate-unlock in modules/nixos/default.nix. # hyprlock on top is a second password (and a Wayland session-lock
before_sleep_cmd = "loginctl lock-session"; # can't be safely dropped after the fact — killing the locker trips
# its "go to a tty" crash failsafe). See nomarchy-lock-before-sleep
# in modules/nixos/default.nix.
after_sleep_cmd = "hyprctl dispatch dpms on"; after_sleep_cmd = "hyprctl dispatch dpms on";
}; };
listener = [ listener = [

View File

@@ -47,6 +47,7 @@
{ mods = "$mod CTRL"; key = "B"; action = "exec, nomarchy-menu bluetooth"; desc = "Bluetooth"; } { mods = "$mod CTRL"; key = "B"; action = "exec, nomarchy-menu bluetooth"; desc = "Bluetooth"; }
{ mods = "$mod CTRL"; key = "S"; action = "exec, nomarchy-menu capture"; desc = "Screenshot / capture"; } { mods = "$mod CTRL"; key = "S"; action = "exec, nomarchy-menu capture"; desc = "Screenshot / capture"; }
{ mods = "$mod CTRL"; key = "A"; action = "exec, nomarchy-menu ask"; desc = "Ask Claude"; } { mods = "$mod CTRL"; key = "A"; action = "exec, nomarchy-menu ask"; desc = "Ask Claude"; }
{ mods = "$mod CTRL"; key = "D"; action = "exec, nomarchy-menu dnd"; desc = "Do Not Disturb toggle"; }
# Focus # Focus
{ mods = "$mod"; key = "H"; action = "movefocus, l"; desc = "Focus left"; } { mods = "$mod"; key = "H"; action = "movefocus, l"; desc = "Focus left"; }

View File

@@ -0,0 +1,25 @@
# Night light — a scheduled blue-light filter via hyprsunset (Hyprland's own
# gamma/temperature tool). Warm at night, identity (no shift) by day;
# hyprsunset's time-based `profile` entries handle the schedule and pick the
# right state on session start. Opt-in via nomarchy.nightlight.enable.
#
# The hyprsunset HM service module is provided by home-manager; this only
# configures it. Override anything with plain services.hyprsunset.* options.
{ config, lib, ... }:
let
cfg = config.nomarchy.nightlight;
in
{
config = lib.mkIf cfg.enable {
services.hyprsunset = {
enable = true;
settings.profile = [
# Daytime: identity = no colour change.
{ time = cfg.sunrise; identity = true; }
# Night: shift to the warm temperature.
{ time = cfg.sunset; temperature = cfg.temperature; }
];
};
};
}

View File

@@ -2,6 +2,60 @@
# configure in their home.nix. Kept small on purpose. # configure in their home.nix. Kept small on purpose.
{ lib, pkgs, ... }: { lib, pkgs, ... }:
let
# One output's layout — turned into a Hyprland `monitor` rule in
# hyprland.nix. Friendlier than raw positional CSV; unset optional fields
# are omitted from the rule.
monitorType = lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.str;
example = "HDMI-A-1";
description = "Output name (see `hyprctl monitors`), or a `desc:<>` match.";
};
resolution = lib.mkOption {
type = lib.types.str;
default = "preferred";
description = "`preferred` | `highres` | `highrr` | `<w>x<h>@<hz>` | `disable`.";
};
position = lib.mkOption {
type = lib.types.str;
default = "auto";
description = "`auto` | `auto-right`/`auto-left`/ | `<x>x<y>`.";
};
scale = lib.mkOption {
type = lib.types.oneOf [ lib.types.str lib.types.int lib.types.float ];
default = 1;
description = "Scale factor (1, 1.5, ) or `auto`.";
};
transform = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = "Rotation 07 (90°/180°/270° = 1/2/3).";
};
mirror = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Mirror another output by name.";
};
bitdepth = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = "Colour bit depth (8 or 10).";
};
vrr = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = "Variable refresh: 0 off, 1 on, 2 fullscreen-only.";
};
extra = lib.mkOption {
type = lib.types.str;
default = "";
description = "Extra comma-separated Hyprland monitor args, appended verbatim.";
};
};
};
in
{ {
options.nomarchy = { options.nomarchy = {
# ── Required ─────────────────────────────────────────────────── # ── Required ───────────────────────────────────────────────────
@@ -42,6 +96,49 @@
description = "XKB variant for the Hyprland session."; description = "XKB variant for the Hyprland session.";
}; };
keyboard.layouts = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [ "de" "fr" ];
description = ''
Extra candidate layouts offered by the interactive new-keyboard
picker. When non-empty, a small watcher runs in the session: when a
keyboard connects that isn't covered by nomarchy.keyboard.devices and
hasn't been chosen before, it pops a rofi picker (these layouts plus
the primary nomarchy.keyboard.layout), applies the choice, and
remembers it per-device (~/.local/state) re-applying automatically
on later reconnects. The runtime-remember complement to the
declarative keyboard.devices; a stateful runtime piece by design.
'';
};
keyboard.devices = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
layout = lib.mkOption {
type = lib.types.str;
description = "XKB layout for this specific keyboard.";
};
variant = lib.mkOption {
type = lib.types.str;
default = "";
description = "XKB variant for this keyboard.";
};
};
});
default = { };
example = lib.literalExpression ''{ "keychron-keychron-k2" = { layout = "de"; }; }'';
description = ''
Per-device keyboard layout. The key is the device name from
`hyprctl devices` (lower-case, hyphenated). Each entry generates a
Hyprland `device` block that overrides the session-wide
nomarchy.keyboard.layout for that keyboard only e.g. an external
keyboard that's physically a different layout than the laptop's
built-in one. Hyprland applies it automatically whenever that
keyboard is connected.
'';
};
package = lib.mkOption { package = lib.mkOption {
type = lib.types.package; type = lib.types.package;
default = pkgs.nomarchy-theme-sync; default = pkgs.nomarchy-theme-sync;
@@ -60,6 +157,54 @@
''; '';
}; };
nightlight = {
enable = lib.mkEnableOption ''
a scheduled blue-light filter (hyprsunset): warm at night, no shift
by day. Opt-in; tune the temperature + sunrise/sunset below'';
temperature = lib.mkOption {
type = lib.types.int;
default = 4000;
example = 3500;
description = "Warm colour temperature (K) applied at night lower is warmer.";
};
sunrise = lib.mkOption {
type = lib.types.str;
default = "07:00";
example = "06:30";
description = "Time (HH:MM) the filter turns OFF daytime, no colour shift.";
};
sunset = lib.mkOption {
type = lib.types.str;
default = "20:00";
example = "21:00";
description = "Time (HH:MM) the filter turns ON warm.";
};
};
monitors = lib.mkOption {
type = lib.types.listOf monitorType;
default = [ ];
example = lib.literalExpression ''
[
{ name = "eDP-1"; position = "0x0"; }
{ name = "HDMI-A-1"; position = "auto-right"; scale = 1; }
]
'';
description = ''
Declarative per-output monitor layout. Each entry becomes a Hyprland
`monitor` rule; Hyprland applies them on hotplug, so a declared
external/dock output arranges itself when connected. The built-in
`,preferred,auto,1` wildcard stays as the fallback for any output you
don't list. For raw control set
`wayland.windowManager.hyprland.settings.monitor` directly instead
(it replaces this). Run `nwg-displays` (nomarchy.displays.enable) to
find the right values interactively.
'';
};
# ── Component toggles ────────────────────────────────────────── # ── Component toggles ──────────────────────────────────────────
hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; }; hyprland.enable = lib.mkEnableOption "Nomarchy's Hyprland configuration" // { default = true; };
waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; }; waybar.enable = lib.mkEnableOption "Nomarchy's Waybar configuration" // { default = true; };
@@ -74,6 +219,7 @@
ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { default = true; }; ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { default = true; };
btop.enable = lib.mkEnableOption "btop with the per-theme nomarchy theme" // { default = true; }; btop.enable = lib.mkEnableOption "btop with the per-theme nomarchy theme" // { default = true; };
stylix.enable = lib.mkEnableOption "Stylix theming for the long tail of apps (GTK, Qt, cursors)" // { default = true; }; stylix.enable = lib.mkEnableOption "Stylix theming for the long tail of apps (GTK, Qt, cursors)" // { default = true; };
displays.enable = lib.mkEnableOption "the nwg-displays interactive monitor arranger (a helper to find nomarchy.monitors values; the declarative config stays the source of truth)" // { default = true; };
# ── Computed (read-only) ─────────────────────────────────────── # ── Computed (read-only) ───────────────────────────────────────
theme = lib.mkOption { theme = lib.mkOption {

View File

@@ -41,9 +41,9 @@ let
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" '' nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
# Nomarchy menu dispatcher thin presentation layer over # Nomarchy menu dispatcher thin presentation layer over
# `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl, # `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl,
# hyprctl and friends. Icons are nf-md glyphs (Pango falls back to a # hyprctl and friends. Menu entries carry real icons from the theme's
# Nerd Font for them). `nomarchy-menu` with no argument shows the # icon set (Papirus) via rofi's per-row icon protocol see row() below.
# module picker. # `nomarchy-menu` with no argument shows the module picker.
urlencode() { urlencode() {
local s="$1" out="" ch i local s="$1" out="" ch i
@@ -58,16 +58,22 @@ let
printf '%s' "$out" printf '%s' "$out"
} }
# Emit one dmenu row with a themed icon: `row "Label" icon-name`. The NUL
# + 0x1f field separator is rofi's per-row icon protocol; printed straight
# to the pipe because bash can't store NUL in a variable. Rendered when
# rofi has show-icons (on globally; passed explicitly on the icon menus).
row() { printf '%s\0icon\x1f%s\n' "$1" "$2"; }
case "''${1:-}" in case "''${1:-}" in
power) power)
choice=$(printf '%s\n' \ choice=$( {
"󰌾 Lock" \ row "Lock" system-lock-screen
"󰍃 Logout" \ row "Logout" system-log-out
"󰤄 Suspend" \ row "Suspend" system-suspend
"󰒲 Hibernate" \ row "Hibernate" system-hibernate
"󰜉 Reboot" \ row "Reboot" system-reboot
"󰐥 Shutdown" \ row "Shutdown" system-shutdown
| rofi -dmenu -p power) || exit 0 } | rofi -dmenu -show-icons -p Power) || exit 0
case "$choice" in case "$choice" in
*Lock) loginctl lock-session ;; *Lock) loginctl lock-session ;;
*Logout) hyprctl dispatch exit ;; *Logout) hyprctl dispatch exit ;;
@@ -134,12 +140,12 @@ let
exec blueman-manager ;; exec blueman-manager ;;
capture) capture)
choice=$(printf '%s\n' \ choice=$( {
"󰆟 Region clipboard" \ row "Region clipboard" applets-screenshooter
"󰆟 Region file" \ row "Region file" applets-screenshooter
"󰍹 Full screen clipboard" \ row "Full screen clipboard" camera-photo
"󰍹 Full screen file" \ row "Full screen file" camera-photo
| rofi -dmenu -p capture) || exit 0 } | rofi -dmenu -show-icons -p Capture) || exit 0
dir="$HOME/Pictures/Screenshots" dir="$HOME/Pictures/Screenshots"
file="$dir/$(date +%Y%m%d-%H%M%S).png" file="$dir/$(date +%Y%m%d-%H%M%S).png"
case "$choice" in case "$choice" in
@@ -164,49 +170,93 @@ let
[ -n "$q" ] || exit 0 [ -n "$q" ] || exit 0
exec ${cfg.terminal} -e npx --yes @anthropic-ai/claude-code@latest "$q" ;; exec ${cfg.terminal} -e npx --yes @anthropic-ai/claude-code@latest "$q" ;;
"") dnd)
entries=( # Toggle swaync Do-Not-Disturb (-d prints the new state). With DND
"󰀻 Apps" # on, notifications are suppressed the Waybar bell-off icon is the
"󰏘 Theme" # cue so only confirm with a toast when turning it back off.
"󰅌 Clipboard" [ "$(swaync-client -d)" = "false" ] \
"󰃬 Calculator" && notify-send "Do Not Disturb off" "Notifications resumed."
"󰈞 Files" exit 0 ;;
"󰒨 Emoji"
"󰖟 Web search" snapshot)
"󰖩 Network" # btrfs-assistant: snapshot browse / diff / restore / rollback over
"󰂯 Bluetooth" # snapper, elevating via polkit. Shipped system-side with
"󰄄 Capture" # nomarchy.system.snapper; self-gate so the entry no-ops elsewhere.
"󰋖 Keybindings" command -v btrfs-assistant >/dev/null 2>&1 \
"󰭹 Ask Claude" || { notify-send "Snapshots" "btrfs-assistant isn't installed (BTRFS snapshots off?)."; exit 0; }
) exec btrfs-assistant ;;
# Power-profile picker only when this is a laptop (battery present)
# running power-profiles-daemon matches the Waybar indicator's tools)
# self-gating; desktops and the TLP backend don't show it. # Tools submenu utilities you invoke. Each leaf is also reachable
bats=(/sys/class/power_supply/BAT*) # directly (SUPER+CTRL+<mnemonic>); this just keeps the root short.
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then choice=$( {
entries+=("󰓅 Power profile") row "Calculator" accessories-calculator
fi row "Clipboard" edit-paste
entries+=("󰐥 Power") row "Emoji" face-smile-big
choice=$(printf '%s\n' "''${entries[@]}" | rofi -dmenu -p menu) || exit 0 row "Files" system-file-manager
row "Web search" system-search
row "Capture" applets-screenshooter
row "Ask Claude" internet-chat
row "Back" go-previous
} | rofi -dmenu -show-icons -p Tools) || exit 0
case "$choice" in case "$choice" in
*Apps*) exec rofi -show drun ;;
*Theme*) exec "$0" theme ;;
*Clipboard*) exec "$0" clipboard ;;
*Calc*) exec "$0" calc ;; *Calc*) exec "$0" calc ;;
*Files*) exec "$0" files ;; *Clipboard*) exec "$0" clipboard ;;
*Emoji*) exec "$0" emoji ;; *Emoji*) exec "$0" emoji ;;
*Files*) exec "$0" files ;;
*Web*) exec "$0" web ;; *Web*) exec "$0" web ;;
*Network*) exec "$0" network ;;
*Bluetooth*) exec "$0" bluetooth ;;
*Capture*) exec "$0" capture ;; *Capture*) exec "$0" capture ;;
*Keybindings*) exec "$0" keybinds ;;
*Ask*) exec "$0" ask ;; *Ask*) exec "$0" ask ;;
*"Power profile"*) exec "$0" power-profile ;; *Back*) exec "$0" ;;
*Power*) exec "$0" power ;; esac ;;
system)
# System submenu connectivity and machine state. Snapshots and the
# power-profile picker self-gate (BTRFS+snapper / laptop on ppd), so
# the submenu shrinks to match the hardware, like the Waybar modules.
bats=(/sys/class/power_supply/BAT*)
choice=$( {
row "Network" network-wireless
row "Bluetooth" bluetooth
row "Do Not Disturb" notification-disabled
command -v btrfs-assistant >/dev/null 2>&1 && row "Snapshots" timeshift
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
row "Power profile" preferences-system-power
fi
row "Back" go-previous
} | rofi -dmenu -show-icons -p System) || exit 0
case "$choice" in
*Network*) exec "$0" network ;;
*Bluetooth*) exec "$0" bluetooth ;;
*"Do Not Disturb"*) exec "$0" dnd ;;
*Snapshots*) exec "$0" snapshot ;;
*"Power profile"*) exec "$0" power-profile ;;
*Back*) exec "$0" ;;
esac ;;
"")
# Root picker grouped into category submenus so the top level stays
# short and scannable. Apps/Theme/Power/Keybindings are frequent enough
# to keep at the top; everything else lives under Tools or System.
choice=$( {
row "Apps" applications-all
row "Theme" preferences-desktop-theme
row "Tools" applications-utilities
row "System" preferences-system
row "Power" system-shutdown
row "Keybindings" preferences-desktop-keyboard
} | rofi -dmenu -show-icons -p Menu) || exit 0
case "$choice" in
*Apps*) exec rofi -show drun ;;
*Theme*) exec "$0" theme ;;
*Tools*) exec "$0" tools ;;
*System*) exec "$0" system ;;
*Power*) exec "$0" power ;;
*Keybindings*) exec "$0" keybinds ;;
esac ;; esac ;;
*) *)
echo "usage: nomarchy-menu [power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|capture|keybinds|ask]" >&2 echo "usage: nomarchy-menu [tools|system|power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|capture|keybinds|ask|dnd|snapshot]" >&2
exit 64 ;; exit 64 ;;
esac esac
''; '';
@@ -238,6 +288,10 @@ in
drun-display-format = "{name}"; drun-display-format = "{name}";
display-drun = "apps"; display-drun = "apps";
display-run = "run"; display-run = "run";
# Size the window to the actual number of rows instead of always
# reserving `lines` (8) — so a 6-entry menu doesn't leave empty space,
# while the app launcher still fills + scrolls past `lines`.
fixed-num-lines = false;
}; };
# Whole-swap themes bring their own rofi.rasi; otherwise the theme # Whole-swap themes bring their own rofi.rasi; otherwise the theme
@@ -290,22 +344,34 @@ in
columns = 1; columns = 1;
dynamic = true; dynamic = true;
scrollbar = false; scrollbar = false;
spacing = px 2; spacing = px 4; # a little air so the rounded zebra rows separate
padding = mkLiteral "8px 0px 0px 0px"; padding = mkLiteral "8px 0px 0px 0px";
}; };
# Rounded, roomy rows with subtle zebra striping: alternate rows
# lift to @surface, the selected row to @accent. normal/alternate/
# selected are rofi's row parities (the `.normal` suffix is the
# match-state, untouched by our menus).
"element" = { "element" = {
padding = mkLiteral "8px 12px"; padding = mkLiteral "10px 14px";
border-radius = px t.ui.rounding; border-radius = px t.ui.rounding;
spacing = px 8; spacing = px 12; # gap between the (larger) icon and the label
}; };
"element selected" = { "element normal.normal" = {
background-color = mkLiteral "@bg";
text-color = mkLiteral "@fg";
};
"element alternate.normal" = {
background-color = mkLiteral "@surface";
text-color = mkLiteral "@fg";
};
"element selected.normal" = {
background-color = mkLiteral "@accent"; background-color = mkLiteral "@accent";
text-color = mkLiteral "@bg"; text-color = mkLiteral "@bg";
}; };
"element-icon" = { "element-icon" = {
background-color = mkLiteral "transparent"; background-color = mkLiteral "transparent";
size = mkLiteral "1em"; size = px t.ui.iconSize; # real icons (Papirus), not 1em font glyphs
}; };
"element-text" = { "element-text" = {
background-color = mkLiteral "transparent"; background-color = mkLiteral "transparent";

View File

@@ -35,6 +35,9 @@ in
ignoreSpace = true; ignoreSpace = true;
expireDuplicatesFirst = true; expireDuplicatesFirst = true;
}; };
# Curated, on by default. The rule (same as rg/fd above): short where
# it helps, but never shadow a real binary — so the git block keeps off
# `gs` (ghostscript), and nothing here masks a coreutil.
shellAliases = { shellAliases = {
cat = "bat --paging=never"; cat = "bat --paging=never";
less = "bat"; less = "bat";
@@ -42,8 +45,42 @@ in
# eza enableZshIntegration aliases ls/ll/la; add a couple extras. # eza enableZshIntegration aliases ls/ll/la; add a couple extras.
lt = "eza --tree --level=2 --icons=auto --group-directories-first"; lt = "eza --tree --level=2 --icons=auto --group-directories-first";
tree = "eza --tree --icons=auto --group-directories-first"; tree = "eza --tree --icons=auto --group-directories-first";
# Navigation — climb without counting dots.
".." = "cd .."; ".." = "cd ..";
"..." = "cd ../.."; "..." = "cd ../..";
"...." = "cd ../../..";
# Git — the verbs you retype all day.
g = "git";
gst = "git status -sb";
ga = "git add";
gaa = "git add -A";
gc = "git commit";
gcm = "git commit -m";
gco = "git checkout";
gsw = "git switch";
gb = "git branch";
gd = "git diff";
gds = "git diff --staged";
gl = "git log --oneline --graph --decorate -20";
glg = "git log --oneline --graph --decorate --all";
gp = "git push";
gpl = "git pull";
gf = "git fetch --all --prune";
# Nix — the flake/store verbs (system/home rebuilds already have the
# sys-update / home-update commands, so they're not re-aliased here).
ns = "nix shell"; # ns nixpkgs#ripgrep
nr = "nix run"; # nr nixpkgs#cowsay
nfu = "nix flake update";
nfc = "nix flake check";
nsearch = "nix search nixpkgs";
ngc = "nix-collect-garbage -d"; # user generations; sudo for system roots
# Misc.
path = "echo $PATH | tr ':' '\\n'";
reload = "exec zsh";
}; };
}; };

View File

@@ -38,6 +38,7 @@ let
gapsOut = 12; gapsOut = 12;
borderSize = 2; borderSize = 2;
rounding = 10; rounding = 10;
iconSize = 36; # rofi launcher + menu icon size (px) for the generated theme
activeOpacity = 1.0; activeOpacity = 1.0;
inactiveOpacity = 0.95; inactiveOpacity = 0.95;
terminalOpacity = 0.96; terminalOpacity = 0.96;

View File

@@ -12,12 +12,23 @@
let let
t = config.nomarchy.theme; t = config.nomarchy.theme;
# Show the active-keyboard-layout indicator only when more than one layout
# is in play — multiple session layouts (comma-separated) or per-device
# overrides (nomarchy.keyboard.devices) — so single-layout bars stay clean.
showLanguage = lib.hasInfix "," config.nomarchy.keyboard.layout
|| config.nomarchy.keyboard.layouts != [ ]
|| config.nomarchy.keyboard.devices != { };
# Power-profile indicator (power-profiles-daemon). Both scripts self- # Power-profile indicator (power-profiles-daemon). Both scripts self-
# gate: they exit silently unless this is a laptop (a battery is # gate: they exit silently unless this is a laptop (a battery is
# present) running PPD, so the module hides itself on desktops and # present) running PPD, so the module hides itself on desktops and
# under the TLP backend — no system→home coupling. Speedometer glyphs # under the TLP backend — no system→home coupling. Speedometer glyphs
# (slow/medium/fast) map to power-saver/balanced/performance. # (slow/medium/fast) map to power-saver/balanced/performance.
powerProfileStatus = pkgs.writeShellScript "nomarchy-powerprofile-status" '' #
# Named writeShellScriptBins (put on PATH via home.packages below) rather
# than bare writeShellScript store paths, so the whole-swap themes' static
# waybar.jsonc can exec them by name too — same as the swaync bell.
powerProfileStatus = pkgs.writeShellScriptBin "nomarchy-powerprofile-status" ''
case "$(ls /sys/class/power_supply/ 2>/dev/null)" in *BAT*) ;; *) exit 0 ;; esac case "$(ls /sys/class/power_supply/ 2>/dev/null)" in *BAT*) ;; *) exit 0 ;; esac
command -v powerprofilesctl >/dev/null 2>&1 || exit 0 command -v powerprofilesctl >/dev/null 2>&1 || exit 0
prof=$(powerprofilesctl get 2>/dev/null) || exit 0 prof=$(powerprofilesctl get 2>/dev/null) || exit 0
@@ -29,7 +40,7 @@ let
printf '{"text":"%s","tooltip":"Power profile: %s","class":"%s"}\n' "$icon" "$prof" "$prof" printf '{"text":"%s","tooltip":"Power profile: %s","class":"%s"}\n' "$icon" "$prof" "$prof"
''; '';
# Cycle to the next profile this machine actually offers (wraps around). # Cycle to the next profile this machine actually offers (wraps around).
powerProfileCycle = pkgs.writeShellScript "nomarchy-powerprofile-cycle" '' powerProfileCycle = pkgs.writeShellScriptBin "nomarchy-powerprofile-cycle" ''
command -v powerprofilesctl >/dev/null 2>&1 || exit 0 command -v powerprofilesctl >/dev/null 2>&1 || exit 0
cur=$(powerprofilesctl get 2>/dev/null) || exit 0 cur=$(powerprofilesctl get 2>/dev/null) || exit 0
avail=$(powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p') avail=$(powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p')
@@ -66,7 +77,9 @@ let
modules-left = [ "hyprland/workspaces" "hyprland/window" ]; modules-left = [ "hyprland/workspaces" "hyprland/window" ];
modules-center = [ "clock" ]; modules-center = [ "clock" ];
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "custom/powerprofile" "battery" ]; modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "custom/powerprofile" ]
++ lib.optional showLanguage "hyprland/language"
++ [ "battery" "custom/notification" ];
"hyprland/workspaces" = { "hyprland/workspaces" = {
format = "{icon}"; format = "{icon}";
@@ -84,6 +97,13 @@ let
tooltip-format = "<tt><small>{calendar}</small></tt>"; tooltip-format = "<tt><small>{calendar}</small></tt>";
}; };
# Active keyboard layout (per focused device) — only placed in
# modules-right when showLanguage (see above).
"hyprland/language" = {
format = "󰌌 {short}";
tooltip = false;
};
pulseaudio = { pulseaudio = {
format = "{icon} {volume}%"; format = "{icon} {volume}%";
format-muted = "󰝟"; format-muted = "󰝟";
@@ -112,10 +132,35 @@ let
}; };
"custom/powerprofile" = { "custom/powerprofile" = {
exec = "${powerProfileStatus}"; exec = "nomarchy-powerprofile-status";
return-type = "json"; return-type = "json";
interval = 5; interval = 5;
on-click = "${powerProfileCycle}"; on-click = "nomarchy-powerprofile-cycle";
};
# swaync notification bell + Do-Not-Disturb state. `-swb` streams JSON
# (text/tooltip/class) on every change, so it tracks count and DND with
# no polling. Left-click toggles the panel; right-click toggles DND.
# The `dnd-*` classes (bell-off glyph) are styled muted below.
"custom/notification" = {
exec = "swaync-client -swb";
return-type = "json";
exec-if = "which swaync-client";
format = "{icon}";
format-icons = {
none = "󰂜";
notification = "󰂚";
dnd-none = "󰂛";
dnd-notification = "󰂛";
inhibited-none = "󰂜";
inhibited-notification = "󰂚";
dnd-inhibited-none = "󰂛";
dnd-inhibited-notification = "󰂛";
};
on-click = "swaync-client -t -sw";
on-click-right = "swaync-client -d -sw";
tooltip = true;
escape = true;
}; };
tray.spacing = 8; tray.spacing = 8;
@@ -164,11 +209,16 @@ let
font-weight: bold; font-weight: bold;
} }
#tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #battery { #tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #language, #battery, #custom-notification {
color: @subtext; color: @subtext;
padding: 0 10px; padding: 0 10px;
} }
/* notifications waiting accent; Do-Not-Disturb muted bell-off */
#custom-notification.notification { color: @accent; }
#custom-notification.dnd-none,
#custom-notification.dnd-notification { color: @muted; }
#pulseaudio.muted { color: @muted; } #pulseaudio.muted { color: @muted; }
#battery.warning { color: @warn; } #battery.warning { color: @warn; }
#battery.critical { color: @bad; } #battery.critical { color: @bad; }
@@ -197,4 +247,9 @@ in
else generatedStyle else generatedStyle
); );
}; };
# The power-profile helpers on PATH, so both the generated bar and the
# whole-swap themes' static waybar.jsonc can exec them by name.
home.packages = lib.optionals config.nomarchy.waybar.enable
[ powerProfileStatus powerProfileCycle ];
} }

View File

@@ -22,7 +22,7 @@ let
''; '';
in in
{ {
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ]; imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ];
config = { config = {
# The safe half of distro branding: distroName flows into # The safe half of distro branding: distroName flows into
@@ -123,23 +123,32 @@ in
networking.networkmanager.enable = lib.mkDefault true; networking.networkmanager.enable = lib.mkDefault true;
# No double-unlock on hibernate. hypridle locks hyprlock before every # No double-unlock on hibernate. Locking the session before sleep is
# sleep (right for suspend), but a hibernate resume is already gated by # right for suspend (resumes from RAM, no other gate), but an encrypted
# the LUKS passphrase entered at boot — so the user would type a password # hibernate already resumes through the LUKS passphrase — a hyprlock on
# twice. Once we're back from an *encrypted* hibernate, drop the now- # top is a second password. And we can't just drop the lock after the
# redundant hyprlock. Ordered After the hibernate service, so it runs # resume: a Wayland session-lock whose client dies without releasing
# post-resume; pulled in only by hibernate.target (suspend, and the # trips hyprlock's "go to a tty" crash failsafe (the compositor keeps the
# RAM-resume phase of suspend-then-hibernate, stay locked — they have no # screen locked for safety). So don't lock before an encrypted hibernate
# passphrase gate). Gated on LUKS: an unencrypted hibernate keeps its lock. # in the first place. hypridle can't tell suspend from hibernate, hence a
systemd.services.nomarchy-hibernate-unlock = # system unit hooked to the sleep targets: it locks on the RAM-resume
lib.mkIf (builtins.attrNames config.boot.initrd.luks.devices != [ ]) { # sleeps always, and on hibernate only when the disk is unencrypted (no
description = "Dismiss hyprlock after resuming from an encrypted hibernate"; # LUKS gate to rely on). Replaces hypridle's old before_sleep_cmd.
after = [ "systemd-hibernate.service" ]; systemd.services.nomarchy-lock-before-sleep =
wantedBy = [ "hibernate.target" ]; let
encrypted = builtins.attrNames config.boot.initrd.luks.devices != [ ];
# Sleeps whose normal resume is from RAM — always lock these.
ramTargets = [ "suspend.target" "hybrid-sleep.target" "suspend-then-hibernate.target" ];
ramServices = [ "systemd-suspend.service" "systemd-hybrid-sleep.service" "systemd-suspend-then-hibernate.service" ];
in
{
description = "Lock the session before sleep (skipped for an encrypted hibernate)";
before = ramServices ++ lib.optional (!encrypted) "systemd-hibernate.service";
wantedBy = ramTargets ++ lib.optional (!encrypted) "hibernate.target";
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
# `-`: a missing hyprlock (idle disabled, or not locked) isn't a failure. # Plural: a system unit has no session of its own to lock.
ExecStart = "-${pkgs.procps}/bin/pkill -x hyprlock"; ExecStart = "${config.systemd.package}/bin/loginctl lock-sessions";
}; };
}; };
@@ -176,7 +185,7 @@ in
# is a clean no-op rather than a failing timer. # is a clean no-op rather than a failing timer.
services.snapper.configs = lib.mkIf services.snapper.configs = lib.mkIf
(cfg.snapper.enable && (config.fileSystems."/".fsType or "") == "btrfs") (cfg.snapper.enable && (config.fileSystems."/".fsType or "") == "btrfs")
{ ({
root = { root = {
SUBVOLUME = "/"; SUBVOLUME = "/";
TIMELINE_CREATE = true; TIMELINE_CREATE = true;
@@ -187,6 +196,39 @@ in
TIMELINE_LIMIT_MONTHLY = "0"; TIMELINE_LIMIT_MONTHLY = "0";
TIMELINE_LIMIT_YEARLY = "0"; TIMELINE_LIMIT_YEARLY = "0";
}; };
}
# /home is user data — snapshot it too when it's its own BTRFS subvolume
# (the installer's @home). Keep the recent hourlies for oops-recovery
# plus dailies, and a month of weeklies; "at least daily" by default.
// lib.optionalAttrs ((config.fileSystems."/home".fsType or "") == "btrfs") {
home = {
SUBVOLUME = "/home";
TIMELINE_CREATE = true;
TIMELINE_CLEANUP = true;
TIMELINE_LIMIT_HOURLY = "5";
TIMELINE_LIMIT_DAILY = "7";
TIMELINE_LIMIT_WEEKLY = "4";
TIMELINE_LIMIT_MONTHLY = "0";
TIMELINE_LIMIT_YEARLY = "0";
};
});
# snapper needs a `.snapshots` subvolume inside each tracked subvolume.
# The installer made one for / (@snapshots → /.snapshots) but not /home,
# so create a nested one there if missing (idempotent via the condition;
# excluded from /home snapshots since BTRFS snapshots are non-recursive).
# Runs at boot so it also fixes already-installed machines — no disko
# change needed.
systemd.services.nomarchy-home-snapshots-subvol = lib.mkIf
(cfg.snapper.enable && (config.fileSystems."/home".fsType or "") == "btrfs") {
description = "Create /home/.snapshots for snapper if missing";
wantedBy = [ "multi-user.target" ];
before = [ "snapper-timeline.service" "snapper-cleanup.service" ];
unitConfig.ConditionPathExists = "!/home/.snapshots";
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.btrfs-progs}/bin/btrfs subvolume create /home/.snapshots";
};
}; };
# ── Fonts ──────────────────────────────────────────────────────── # ── Fonts ────────────────────────────────────────────────────────
@@ -276,7 +318,10 @@ in
--cleanup-algorithm number --cleanup-algorithm number
echo "Rebuilding..." echo "Rebuilding..."
nixos-rebuild switch --flake /etc/nixos#default "$@" nixos-rebuild switch --flake /etc/nixos#default "$@"
''); '')
# The desktop snapshot manager (browse / diff / restore / rollback over
# snapper, elevating via polkit) — what `nomarchy-menu snapshot` launches.
++ lib.optional cfg.snapper.enable pkgs.btrfs-assistant;
# Don't let boot entries fill the ESP over the years. # Don't let boot entries fill the ESP over the years.
boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10; boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10;
@@ -291,6 +336,11 @@ in
settings = { settings = {
experimental-features = [ "nix-command" "flakes" ]; experimental-features = [ "nix-command" "flakes" ];
auto-optimise-store = lib.mkDefault true; auto-optimise-store = lib.mkDefault true;
# The downstream flake (~/.nomarchy) is meant to be a live working
# tree: nomarchy-theme-sync rewrites theme-state.json on every
# switch (and you needn't commit each tweak), so the "Git tree is
# dirty" warning fires on every rebuild and is pure noise here.
warn-dirty = lib.mkDefault false;
}; };
gc = { gc = {
automatic = lib.mkDefault true; automatic = lib.mkDefault true;

280
modules/nixos/services.nix Normal file
View File

@@ -0,0 +1,280 @@
# Opt-in services & integrations — off by default, one toggle each, enabled
# in system.nix (see the commented examples in the downstream template).
# These are system-side: their own `nomarchy.services.*` namespace, distinct
# from the distro's core `nomarchy.system.*` toggles. `username` (from
# specialArgs) is read lazily, only where a service needs it.
{ config, lib, pkgs, ... }@args:
let
cfg = config.nomarchy.services;
in
{
options.nomarchy.services = {
tailscale.enable = lib.mkEnableOption ''
Tailscale, the mesh VPN ships the daemon; authenticate once with
`sudo tailscale up`'';
syncthing.enable = lib.mkEnableOption ''
Syncthing continuous file sync, running as the login user add
folders and devices in the web UI at http://127.0.0.1:8384'';
podman.enable = lib.mkEnableOption ''
Podman for rootless containers, with `docker` aliased to it and DNS on
the default network; the login user gets a subuid/subgid range'';
flatpak.enable = lib.mkEnableOption ''
Flatpak, with the Flathub remote added system-wide install apps with
`flatpak install flathub <app>`'';
pika.enable = lib.mkEnableOption ''
Pika Backup, a friendly GUI for scheduled Borg backups (the app;
configure repositories and schedules inside it)'';
steam.enable = lib.mkEnableOption ''
Steam (Valve's game store/launcher) via programs.steam which wires up
the 32-bit graphics libraries, controller udev rules and Remote-Play
firewall ports a bare package can't. Unfree'';
libvirt.enable = lib.mkEnableOption ''
libvirt/KVM virtualisation with the virt-manager GUI runs the
libvirtd daemon and adds the login user to the `libvirtd` group, so VMs
can be managed without root'';
obs.enable = lib.mkEnableOption ''
OBS Studio for screen recording and streaming, plus a v4l2loopback
virtual camera so an OBS scene can be selected as a webcam in Zoom,
Teams or a browser (screen capture itself already works over the
desktop's PipeWire portal)'';
docker.enable = lib.mkEnableOption ''
Docker (rootful) with the login user in the `docker` group. Mutually
exclusive with the podman toggle's docker-compat (both provide the
`docker` command) enable only one'';
kdeconnect.enable = lib.mkEnableOption ''
KDE Connect for phone integration shared clipboard, notifications and
file transfer with a paired phone; opens the firewall ports it needs'';
gamemode.enable = lib.mkEnableOption ''
Feral GameMode a daemon games ask to apply a performance CPU governor
and IO/nice priorities while running (via `gamemoderun`)'';
adb.enable = lib.mkEnableOption ''
the Android platform tools (adb/fastboot) for flashing/debugging phones.
systemd 258 applies the device uaccess udev rules automatically, so the
tools alone are enough no group dance'';
wireshark.enable = lib.mkEnableOption ''
Wireshark (the Qt GUI) for packet capture, with the login user in the
`wireshark` group so capture works without root'';
ollama.enable = lib.mkEnableOption ''
Ollama, a local LLM runtime serving an API on 127.0.0.1:11434 (pair it
with the lmstudio/alpaca GUIs). CPU by default set
`services.ollama.acceleration` natively for GPU offload'';
printing.enable = lib.mkEnableOption ''
CUPS printing with Avahi/mDNS, so network printers are auto-discovered
(add vendor drivers via `services.printing.drivers`)'';
openrgb.enable = lib.mkEnableOption ''
the OpenRGB daemon and GUI for controlling RGB lighting on peripherals
and (where supported) motherboards installs the udev rules for device
access. Some motherboard controllers also need `hardware.i2c.enable`'';
restic = {
enable = lib.mkEnableOption ''
a scheduled (daily) restic backup headless, complementing the Pika
GUI. Backs up `paths` with sane retention (7 daily / 4 weekly /
6 monthly); needs a `repository` and a `passwordFile` (below). List
and restore with the generated `restic-nomarchy` wrapper (repo +
password baked in), e.g. `restic-nomarchy snapshots`'';
repository = lib.mkOption {
type = lib.types.str;
default = "";
example = "/mnt/backup/restic";
description = ''
The restic repository as a path or URL e.g. `/mnt/backup/restic`,
`sftp:user@host:/srv/restic`, `b2:bucket:/path`. Required when
`restic.enable` is set.
'';
};
passwordFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/etc/nomarchy/restic-password";
description = ''
Path to a file holding the repository password. Give an absolute
path that lives ON THE MACHINE not a `./file` in the flake, which
would copy the secret into the world-readable Nix store. Required
when `restic.enable` is set.
'';
};
paths = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "/home" ];
description = "Paths to include in the backup.";
};
};
};
config = lib.mkMerge [
(lib.mkIf cfg.tailscale.enable {
services.tailscale.enable = true;
})
(lib.mkIf cfg.syncthing.enable {
services.syncthing = {
enable = true;
user = args.username;
# Manage folders/devices in the GUI; don't let the (empty) declarative
# config wipe them on a rebuild.
overrideDevices = false;
overrideFolders = false;
guiAddress = "127.0.0.1:8384";
};
})
(lib.mkIf cfg.podman.enable {
virtualisation.podman = {
enable = true;
dockerCompat = lib.mkDefault true; # `docker` → podman (off if you run real docker)
defaultNetwork.settings.dns_enabled = true; # DNS for rootless containers
};
# Rootless podman needs a subuid/subgid range for the user.
users.users.${args.username}.autoSubUidGidRange = lib.mkDefault true;
})
(lib.mkIf cfg.flatpak.enable {
services.flatpak.enable = true;
# Flatpak ships no remotes; add Flathub system-wide. Idempotent
# (--if-not-exists), and it reads the .flatpakrepo over the network, so
# it waits for connectivity and simply retries next boot if offline.
systemd.services.nomarchy-flathub = {
description = "Add the Flathub Flatpak remote";
wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
path = [ pkgs.flatpak ];
serviceConfig.Type = "oneshot";
script = ''
flatpak remote-add --system --if-not-exists flathub \
https://dl.flathub.org/repo/flathub.flatpakrepo
'';
};
})
(lib.mkIf cfg.pika.enable {
environment.systemPackages = [ pkgs.pika-backup ];
})
(lib.mkIf cfg.steam.enable {
# The Steam package alone misses the system bits a game needs: the
# 32-bit graphics stack, controller udev rules, Remote-Play ports.
programs.steam.enable = true;
})
(lib.mkIf cfg.libvirt.enable {
virtualisation.libvirtd.enable = true;
programs.virt-manager.enable = true;
# Group membership lets the user reach the system libvirtd socket
# (manage VMs) without sudo. Merges with the user's other extraGroups.
users.users.${args.username}.extraGroups = [ "libvirtd" ];
})
(lib.mkIf cfg.obs.enable {
environment.systemPackages = [ pkgs.obs-studio ];
# Virtual camera: OBS streams a scene into a v4l2 loopback device that
# Zoom/Teams/browsers then pick up as a webcam. exclusive_caps=1 is the
# bit that makes those apps actually list the device.
boot.extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ];
boot.kernelModules = [ "v4l2loopback" ];
boot.extraModprobeConfig = ''
options v4l2loopback devices=1 video_nr=10 card_label="OBS Virtual Camera" exclusive_caps=1
'';
})
(lib.mkIf cfg.docker.enable {
virtualisation.docker.enable = true;
users.users.${args.username}.extraGroups = [ "docker" ];
# Podman's docker-compat and real Docker both claim the `docker`
# command — guard against enabling both (unless you turn that off).
assertions = [{
assertion = !(cfg.podman.enable && config.virtualisation.podman.dockerCompat);
message = ''
nomarchy.services: docker and podman both provide the `docker`
command. Enable only one, or set
`virtualisation.podman.dockerCompat = false`.
'';
}];
})
(lib.mkIf cfg.kdeconnect.enable {
programs.kdeconnect.enable = true; # installs the app and opens its ports
})
(lib.mkIf cfg.gamemode.enable {
programs.gamemode.enable = true;
})
(lib.mkIf cfg.adb.enable {
# `programs.adb` was removed — systemd ≥258 handles the device uaccess
# udev rules, so the tools package is all that's needed now.
environment.systemPackages = [ pkgs.android-tools ];
})
(lib.mkIf cfg.wireshark.enable {
programs.wireshark = {
enable = true;
package = pkgs.wireshark; # the Qt GUI (the module defaults to the CLI)
};
users.users.${args.username}.extraGroups = [ "wireshark" ];
})
(lib.mkIf cfg.ollama.enable {
services.ollama.enable = true;
})
(lib.mkIf cfg.printing.enable {
services.printing.enable = true;
# mDNS so network printers show up without manual setup.
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};
})
(lib.mkIf cfg.openrgb.enable {
services.hardware.openrgb.enable = true;
})
(lib.mkIf cfg.restic.enable {
assertions = [
{
assertion = cfg.restic.repository != "";
message = "nomarchy.services.restic.repository must be set when restic is enabled.";
}
{
assertion = cfg.restic.passwordFile != null;
message = "nomarchy.services.restic.passwordFile must be set when restic is enabled.";
}
];
services.restic.backups.nomarchy = {
repository = cfg.restic.repository;
passwordFile = cfg.restic.passwordFile;
paths = cfg.restic.paths;
initialize = true; # create the repo on first run if it doesn't exist
extraBackupArgs = [ "--exclude-caches" ];
pruneOpts = [ "--keep-daily 7" "--keep-weekly 4" "--keep-monthly 6" ];
timerConfig = {
OnCalendar = "daily";
Persistent = true; # catch up a missed run after downtime
};
};
})
];
}

View File

@@ -444,15 +444,14 @@ cat > "$FLAKE_DIR/system.nix" <<EOF
time.timeZone = "$TIMEZONE"; time.timeZone = "$TIMEZONE";
i18n.defaultLocale = "$LOCALE"; i18n.defaultLocale = "$LOCALE";
# One keyboard layout everywhere: xkb is the source of truth, the # One keyboard layout everywhere: xkb is the source of truth, and the
# virtual console derives from it (greeter/ttys), and the systemd # distro defaults (console.useXkbConfig + the systemd initrd, set in
# initrd applies it to the LUKS passphrase prompt too (the classic # the nomarchy module) derive the virtual console and the LUKS
# script initrd cannot). The Hyprland session reads the same layout # passphrase prompt from it — so only the chosen layout is written here.
# from nomarchy.keyboard.* in home.nix. # The Hyprland session reads the same layout from nomarchy.keyboard.* in
# home.nix.
services.xserver.xkb.layout = "$KB_LAYOUT"; services.xserver.xkb.layout = "$KB_LAYOUT";
services.xserver.xkb.variant = "$KB_VARIANT"; services.xserver.xkb.variant = "$KB_VARIANT";
console.useXkbConfig = true;
boot.initrd.systemd.enable = true;
# Your login user — \`username\` flows in from flake.nix automatically. # Your login user — \`username\` flows in from flake.nix automatically.
users.users.\${username} = { users.users.\${username} = {

View File

@@ -5,15 +5,136 @@
{ pkgs, ... }: { pkgs, ... }:
{ {
# Examples: # ── Overrides (the desktop is on by default; tweak or opt out) ──────
# nomarchy.terminal = "kitty"; # swap the default terminal # nomarchy.terminal = "kitty"; # swap the default terminal
# nomarchy.waybar.enable = false; # bring your own bar # nomarchy.waybar.enable = false; # bring your own bar
# nomarchy.stylix.enable = false; # opt out of GTK/Qt theming # nomarchy.stylix.enable = false; # opt out of GTK/Qt theming
# nomarchy.keyboard.layout = "de"; # session keyboard (pair with # nomarchy.keyboard.layout = "de"; # session keyboard (set the matching
# # xkb + console.useXkbConfig in # # services.xserver.xkb.layout in
# # system.nix for tty/LUKS) # # system.nix too; the console + LUKS
# # prompt follow it automatically)
# ── Opt-in features — uncomment and tweak to enable ─────────────────
# nomarchy.nightlight = { # scheduled blue-light filter (hyprsunset)
# enable = true;
# temperature = 4000; # night warmth in K — lower is warmer
# sunset = "20:00";
# sunrise = "07:00";
# };
#
# nomarchy.monitors = [ # declarative per-output layout, hotplug-applied
# { name = "eDP-1"; position = "0x0"; }
# { name = "HDMI-A-1"; position = "auto-right"; }
# ]; # names from `hyprctl devices`
#
# nomarchy.keyboard.devices = { # a specific keyboard's own layout
# "keychron-keychron-k2" = { layout = "de"; };
# };
#
# nomarchy.keyboard.layouts = [ "de" "fr" ]; # rofi-prompt for a layout when a
# # new keyboard connects + remember it
# ── Application suite ───────────────────────────────────────────────
# A starter complete-workstation set, installed for your user — yours to
# curate. Delete a line to slim the machine; uncomment a suggestion (or
# add your own) to extend it. The desktop itself — terminal, editor
# keybinds, theming — comes from the modules; these are the heavier
# standalone apps the distro doesn't impose by default.
#
# Apps that need system setup beyond a package (Steam's 32-bit stack,
# a daemon, group membership, a kernel module) are NOT here — they're
# opt-in service toggles in system.nix instead (nomarchy.services.* —
# steam, libvirt, obs, docker, gamemode, kdeconnect, adb, wireshark,
# ollama, printing).
home.packages = with pkgs; [ home.packages = with pkgs; [
libreoffice-fresh # office suite (documents, sheets, slides)
vscode # code editor (unfree; allowUnfree is on)
gimp # raster image editor
inkscape # vector graphics
mpv # media player
amberol # music player (local library; streaming → spotify below)
# ── More apps — uncomment to add ─────────────────────────────────
# Web browsers
# firefox # firefox
# chromium
# ungoogled-chromium
# brave # (unfree)
# google-chrome # (unfree)
# Communication
# signal-desktop
# telegram-desktop
# element-desktop # Matrix
# vesktop # Discord (themeable client)
# slack # (unfree)
# zoom-us # (unfree)
# teams-for-linux # community client (no official Linux app)
# thunderbird # email
# Office & documents
# onlyoffice-desktopeditors # stronger MS-Office fidelity
# kdePackages.okular # PDF viewer
# xournalpp # PDF annotation
# pdfarranger
# calibre # ebook library
# texliveFull # full LaTeX toolchain — multi-GB (texliveMedium is lighter)
# Notes / research
# logseq
# obsidian # (unfree)
# joplin-desktop
# anki-bin # flashcards
# zotero # reference manager
# Code & data
# zed-editor
# lazygit
# dbeaver-bin # database GUI
# bruno # API client
# distrobox # containerized dev shells
# Graphics & design
# krita # digital painting
# blender # 3D
# darktable # RAW photo
# Audio / video
# audacity
# shotcut # video editor
# kdePackages.kdenlive # video editor (pulls in KDE libs)
# handbrake # transcoder
# Media
# vlc
# spotify # (unfree)
# newsflash # RSS reader
# Gaming (Steam + GameMode are service toggles in system.nix)
# lutris
# heroic # Epic/GOG launcher
# bottles # Wine prefixes
# prismlauncher # Minecraft
# protonup-qt # Proton-GE manager
# mangohud # in-game overlay
# Files, sync & torrents
# nextcloud-client
# localsend # AirDrop-like transfer
# qbittorrent
# Security
# keepassxc
# bitwarden-desktop
# System
# gnome-disk-utility
# gparted
# baobab # disk usage
# wireguard-tools # WireGuard CLI (NetworkManager imports .conf tunnels natively)
# Local AI (Ollama is a service toggle in system.nix)
# lmstudio # (unfree; large closure)
# alpaca # GTK Ollama UI
]; ];
} }

View File

@@ -18,9 +18,41 @@
extraGroups = [ "wheel" "networkmanager" "video" "input" ]; extraGroups = [ "wheel" "networkmanager" "video" "input" ];
}; };
# Examples: # ── Overrides (uncomment to change) ─────────────────────────────────
# nomarchy.system.greeter.enable = false; # bring your own login manager # nomarchy.system.greeter.enable = false; # bring your own login manager
# environment.systemPackages = [ pkgs.htop ]; # environment.systemPackages = [ pkgs.htop ];
# ── Opt-in features — uncomment and tweak to enable ─────────────────
# nomarchy.system.power = { # active power management (laptops)
# enable = true;
# backend = "ppd"; # "ppd" (default) or "tlp"
# laptop = true; # required by batteryChargeLimit
# batteryChargeLimit = 80; # cap charging for longevity
# thermal.enable = true; # thermald (Intel CPUs)
# };
#
# nomarchy.services.tailscale.enable = true; # mesh VPN — then `sudo tailscale up`
# nomarchy.services.syncthing.enable = true; # file sync — GUI at http://127.0.0.1:8384
# nomarchy.services.podman.enable = true; # rootless containers (docker → podman)
# nomarchy.services.flatpak.enable = true; # Flatpak + the Flathub remote
# nomarchy.services.pika.enable = true; # Pika Backup (GUI Borg backups)
# nomarchy.services.steam.enable = true; # Steam (32-bit libs, controllers, ports)
# nomarchy.services.libvirt.enable = true; # libvirt/KVM + virt-manager GUI
# nomarchy.services.obs.enable = true; # OBS Studio + v4l2loopback virtual camera
# nomarchy.services.docker.enable = true; # Docker rootful (not alongside podman)
# nomarchy.services.kdeconnect.enable = true;# KDE Connect phone integration (opens ports)
# nomarchy.services.gamemode.enable = true; # Feral GameMode performance daemon
# nomarchy.services.adb.enable = true; # Android adb/fastboot tools
# nomarchy.services.wireshark.enable = true; # Wireshark GUI (wireshark group, no root)
# nomarchy.services.ollama.enable = true; # local LLM runtime (127.0.0.1:11434)
# nomarchy.services.printing.enable = true; # CUPS + Avahi network printer discovery
# nomarchy.services.openrgb.enable = true; # RGB peripheral/motherboard lighting daemon
# nomarchy.services.restic = { # scheduled (daily) restic backup
# enable = true;
# repository = "/mnt/backup/restic"; # path or URL (sftp:/b2:/…)
# passwordFile = "/etc/nomarchy/restic-password"; # absolute path, NOT in the flake
# paths = [ "/home" ];
# };
system.stateVersion = "26.05"; system.stateVersion = "26.05";
} }

View File

@@ -29,7 +29,8 @@ window {
border-color: @yellow; border-color: @yellow;
border-radius: 15px; border-radius: 15px;
width: 40%; width: 40%;
height: 50%; /* No fixed height — size to content (fixed-num-lines:false) so every
* entry shows without scrolling and there's no empty space below. */
location: center; location: center;
anchor: center; anchor: center;
x-offset: 0; x-offset: 0;
@@ -90,6 +91,12 @@ element {
spacing: 15px; spacing: 15px;
} }
/* Zebra striping: every other row lifts with a faint light wash over the
* dark window (bg0 at ~12% alpha). The selected row below still wins. */
element alternate.normal {
background-color: #fdf6e31f;
}
element-icon { element-icon {
background-color: transparent; background-color: transparent;
text-color: inherit; text-color: inherit;

View File

@@ -48,8 +48,10 @@ window#waybar {
#workspaces, #workspaces,
#pulseaudio, #pulseaudio,
#network, #network,
#custom-powerprofile,
#battery, #battery,
#tray, #tray,
#custom-notification,
#custom-powermenu { #custom-powermenu {
background-color: @bg0; background-color: @bg0;
color: @fg; color: @fg;

View File

@@ -11,7 +11,7 @@
"modules-left": ["custom/launcher", "clock", "clock#date"], "modules-left": ["custom/launcher", "clock", "clock#date"],
"modules-center": ["hyprland/workspaces"], "modules-center": ["hyprland/workspaces"],
"modules-right": ["pulseaudio", "network", "battery", "tray", "custom/powermenu"], "modules-right": ["pulseaudio", "network", "custom/powerprofile", "battery", "tray", "custom/notification", "custom/powermenu"],
"hyprland/workspaces": { "hyprland/workspaces": {
"disable-scroll": true, "disable-scroll": true,
@@ -80,6 +80,34 @@
"tray": { "spacing": 8 }, "tray": { "spacing": 8 },
"custom/powerprofile": {
"return-type": "json",
"interval": 5,
"exec": "nomarchy-powerprofile-status",
"on-click": "nomarchy-powerprofile-cycle"
},
"custom/notification": {
"format": "{icon}",
"return-type": "json",
"exec": "swaync-client -swb",
"exec-if": "which swaync-client",
"escape": true,
"tooltip": true,
"on-click": "swaync-client -t -sw",
"on-click-right": "swaync-client -d -sw",
"format-icons": {
"none": "",
"notification": "",
"dnd-none": "",
"dnd-notification": "",
"inhibited-none": "",
"inhibited-notification": "",
"dnd-inhibited-none": "",
"dnd-inhibited-notification": ""
}
},
"custom/powermenu": { "custom/powermenu": {
"format": "", "format": "",
"on-click": "nomarchy-menu power", "on-click": "nomarchy-menu power",

View File

@@ -29,7 +29,8 @@ window {
border-color: @yellow; border-color: @yellow;
border-radius: 15px; border-radius: 15px;
width: 40%; width: 40%;
height: 50%; /* No fixed height — size to content (fixed-num-lines:false) so every
* entry shows without scrolling and there's no empty space below. */
location: center; location: center;
anchor: center; anchor: center;
x-offset: 0; x-offset: 0;
@@ -90,6 +91,12 @@ element {
spacing: 15px; spacing: 15px;
} }
/* Zebra striping: every other row lifts with a faint dark wash over the
* cream window (bg0 at ~12% alpha). The selected row below still wins. */
element alternate.normal {
background-color: #2d353b1f;
}
element-icon { element-icon {
background-color: transparent; background-color: transparent;
text-color: inherit; text-color: inherit;

View File

@@ -54,8 +54,10 @@ window#waybar {
#workspaces, #workspaces,
#pulseaudio, #pulseaudio,
#network, #network,
#custom-powerprofile,
#idle_inhibitor, #idle_inhibitor,
#battery, #battery,
#custom-notification,
#custom-powermenu { #custom-powermenu {
background-color: @bg0; background-color: @bg0;
color: @fg; color: @fg;

View File

@@ -11,7 +11,7 @@
"modules-left": ["custom/nomarchy", "clock", "clock#date"], "modules-left": ["custom/nomarchy", "clock", "clock#date"],
"modules-center": ["hyprland/workspaces"], "modules-center": ["hyprland/workspaces"],
"modules-right": ["idle_inhibitor", "pulseaudio", "network", "battery", "tray", "custom/powermenu"], "modules-right": ["idle_inhibitor", "pulseaudio", "network", "custom/powerprofile", "battery", "tray", "custom/notification", "custom/powermenu"],
"hyprland/workspaces": { "hyprland/workspaces": {
"disable-scroll": true, "disable-scroll": true,
@@ -87,6 +87,34 @@
"tray": { "spacing": 8 }, "tray": { "spacing": 8 },
"custom/powerprofile": {
"return-type": "json",
"interval": 5,
"exec": "nomarchy-powerprofile-status",
"on-click": "nomarchy-powerprofile-cycle"
},
"custom/notification": {
"format": "{icon}",
"return-type": "json",
"exec": "swaync-client -swb",
"exec-if": "which swaync-client",
"escape": true,
"tooltip": true,
"on-click": "swaync-client -t -sw",
"on-click-right": "swaync-client -d -sw",
"format-icons": {
"none": "",
"notification": "",
"dnd-none": "",
"dnd-notification": "",
"inhibited-none": "",
"inhibited-notification": "",
"dnd-inhibited-none": "",
"dnd-inhibited-notification": ""
}
},
"custom/powermenu": { "custom/powermenu": {
"format": "", "format": "",
"on-click": "nomarchy-menu power", "on-click": "nomarchy-menu power",