Add opt-in nomarchy.updates (modules/home/updates.nix): a nomarchy-updates checker on a systemd user timer (interval, default daily) that surfaces a Waybar indicator + a notification when updates are available, without ever changing anything (you still run sys-update / flatpak update). It counts: - flake inputs behind upstream — only the flake's DIRECT inputs (root.inputs: nixpkgs, the Nomarchy input, home-manager, stylix…), not the transitive closure, via `git ls-remote` vs the locked rev. Offline → skipped, never a false alarm. - Flatpak updates, when the flatpak CLI is present (.flatpak, gated on services.flatpak being on) — Bernardo's suggestion. Waybar custom/updates self-gates (hidden until the timer finds something; accent " N", signal 9 for instant refresh), in the generated bar and both summer whole-swaps. The notification fires only when the count grows, so a daily timer doesn't nag. Click → the upgrade flow in a terminal (sys-update / flatpak update, each confirmed). nomarchy-updates ships always on PATH and self-gates so the static whole-swap bars can exec it even when the feature is off. Verified: flake check clean; home generation builds; the jq direct-input filter + status self-gate exercised against the real flake.lock; script passes bash -n. Pending an on-machine check (ls-remote/notify/timer need a live session). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nomarchy
The rock-solid reproducibility of NixOS 26.05. The out-of-the-box polish of Omarchy/Omakub. One JSON file rules the look of the entire desktop; every theme change is a Home Manager generation — atomic, rollbackable, never partial.
┌──────────────────────────────────────────────────────────────────────────┐
│ theme-state.json (single source of truth) │
│ lives INSIDE your flake checkout, git-tracked │
└───────────────────────────────────┬──────────────────────────────────────┘
│
nomarchy-theme-sync apply gruvbox
1. merges the preset into the JSON (atomic write)
2. runs `home-manager switch` (no sudo, no system rebuild)
│
▼ pure read (nomarchy.stateFile)
┌──────────────────────────────────────────────────────────────────────────┐
│ Home Manager bakes EVERYTHING into one read-only generation: │
│ Hyprland (colors/gaps/borders) Waybar (palette or whole-swap) │
│ Ghostty (full ANSI palette) btop (asset or generated) │
│ Stylix → GTK, Qt, cursors, fonts │
└───────────────────────────────────┬──────────────────────────────────────┘
▼
wallpaper via awww (the one runtime piece:
applied post-switch + at session start, `bg next` cycles)
1. Layout
Flat on purpose. Two module trees, one options file each, no hidden layers.
.
├── flake.nix # inputs + the downstream API (exports below)
├── lib.nix # nomarchy.lib.mkFlake — one-call downstream wrapper
├── theme-state.json # ★ THE single source of truth (git-tracked!)
├── themes/ # 21 presets: <slug>.json + optional <slug>/ assets
│ ├── nord.json # palette (required, works alone)
│ └── nord/ # assets (optional, fixed filenames)
│ ├── backgrounds/ # wallpapers (auto-picked, SUPER+SHIFT+T cycles)
│ ├── btop.theme # hand-made config drop (else generated)
│ └── waybar.css # whole-swap: replaces the generated bar style
├── modules/
│ ├── nixos/ # the distro, system side
│ │ ├── default.nix # Hyprland session, Pipewire, greetd, fonts
│ │ ├── options.nix # nomarchy.system.* toggles
│ │ ├── plymouth.nix # boot splash, tinted from the JSON
│ │ ├── file-manager.nix # Thunar GUI + gvfs/tumbler/udisks2
│ │ └── power.nix # power-profiles-daemon/TLP, thermald, charge limit
│ └── home/ # the distro, user side
│ ├── default.nix # entry point
│ ├── options.nix # nomarchy.* option surface
│ ├── theme.nix # JSON ingestion + wallpaper hook
│ ├── stylix.nix # GTK/Qt/cursors/fonts from the same JSON
│ ├── hyprland.nix # all JSON-driven
│ ├── waybar.nix
│ ├── ghostty.nix
│ ├── btop.nix
│ ├── rofi.nix # launcher + nomarchy-menu (calc, emoji, clip…)
│ ├── keybinds.nix # single source: Hyprland binds + SUPER+? sheet
│ ├── swaync.nix # notifications, same JSON
│ ├── idle.nix # hyprlock + hypridle, same JSON
│ ├── yazi.nix # flagship TUI file manager + plugins
│ ├── osd.nix # swayosd volume/brightness OSD
│ ├── shell.nix # zsh + starship + bat/eza/zoxide
│ ├── keys.nix # gpg-agent fronting SSH, pinentry-qt
│ └── fastfetch.nix # themed Nomarchy logo + system info
├── hosts/
│ ├── default/ # reference machine (thin: boot, user, hostname)
│ └── live.nix # bootable live ISO (try the distro, no install)
├── pkgs/
│ ├── nomarchy-theme-sync/ # state writer + rebuild dispatcher (Python)
│ └── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake)
├── templates/downstream/ # `nix flake init -t` starter for users
├── docs/TESTING.md # how to verify changes (incl. AI-agent rules)
├── docs/OVERRIDES.md # how downstream users override defaults
├── docs/ROADMAP.md # forward-looking plans + shipped-fixes log
└── tools/ # maintainer-only
├── import-palettes.py # converts old-distro themes → JSON + assets
├── test-live-iso.sh # build the ISO + boot it in QEMU
├── test-install.sh # full offline-install regression in QEMU
└── vm/ # headless-VM helpers (QMP keys, VNC shots,
# offline-pin gap analysis)
Rule of thumb: modules/ is the distro (reusable, no machine specifics),
hosts/ is a machine, themes/ is data, pkgs/ is code, tools/ is
maintainer-only. If a new file doesn't obviously belong to one of those, it
probably shouldn't exist.
2. Try it first (live ISO)
Boot the full desktop from a USB stick or VM without installing anything:
nix build .#nixosConfigurations.nomarchy-live.config.system.build.isoImage
# → result/iso/*.iso — dd to a stick, or boot it in QEMU:
tools/test-live-iso.sh
The live session auto-logs-in, seeds the flake at ~/.nomarchy, and pins
the locked inputs into the ISO store — so theme switching (including the
home-manager switch it triggers) works offline, exactly like on an
installed system. Verification checklist: docs/TESTING.md.
Like what you see? nomarchy-install (in a terminal) walks you through
installing to disk: pick a disk, LUKS2 full-disk encryption by default
(in exchange the desktop logs in passwordless — the passphrase already
gates the machine), user + hostname + timezone, hardware autodetection
(DMI → nixos-hardware profile), a hibernation-ready swapfile sized to RAM,
then disko partitions (GPT + ESP + BTRFS subvolumes incl. @snapshots —
snapper timeline snapshots are on) and nixos-install runs — without a
network when the ISO was built from a clean tree (the target's
flake.lock is composed from the rev the ISO carries). The installed
machine gets the standard downstream layout: the flake at ~/.nomarchy
(/etc/nixos symlinks to it), one mkFlake call, your
system.nix/home.nix. First boot lands in the fully themed desktop —
the installer pre-activates the Home Manager generation. UEFI only for now.
3. Using Nomarchy on your machine (downstream)
Nomarchy is consumed as a flake input — you never fork or edit this repo:
mkdir my-machine && cd my-machine
nix flake init -t "git+https://git.bemagri.xyz/bernardo/nomarchy.git?ref=v1"
You own two files day-to-day: system.nix and home.nix (plus
theme-state.json, written by the CLI). Your flake.nix is set up once —
later by the installer — and never hand-edited; it's a single call:
outputs = { nomarchy, ... }:
nomarchy.lib.mkFlake {
src = ./.;
username = "me";
hardwareProfile = "framework-13-7040-amd"; # optional, nixos-hardware name
};
mkFlake arg |
Default | Purpose |
|---|---|---|
src |
— (required) | Your flake directory (./.) |
username |
— (required) | Login name; flows into system.nix and names the HM config |
hardwareProfile |
null |
One nixos-hardware module name, or a list of them (pinned + tested by Nomarchy; unknown names fail with suggestions) |
system |
"x86_64-linux" |
Platform |
(Power users can skip mkFlake and wire nixosModules.nomarchy /
homeModules.nomarchy / overlays.default by hand — the wrapper is sugar.)
Two deliberately separate rebuild paths:
sudo nixos-rebuild switch --flake .#default # system: rare
home-manager switch --flake .#me # desktop: every theme change, no sudo
Day-to-day you'll use the shipped shortcuts instead:
sys-update # nix flake update + system rebuild (BTRFS snapshot first when available)
home-update # home-manager switch (no flake update, no sudo)
Order matters when pulling distro updates. home-update does not
touch the lock — it rebuilds the desktop against the current
flake.lock. A new Nomarchy revision (new keybinds, theming, modules)
arrives only when the lock is updated, which sys-update does
(nix flake update). So to pull an update that affects the desktop layer:
run sys-update first (updates the lock + rebuilds the system), then
home-update (re-applies the desktop against the new lock). Doing them in
the other order rebuilds the desktop against the old inputs and silently
skips the new home-side changes. After a home-side keybind/config change,
also hyprctl reload (or relogin) so the running session re-reads it.
Override anything via the nomarchy.* surface or plain NixOS/HM options:
appearance (gaps/colors/fonts) changes through nomarchy-theme-sync,
behaviour (input/misc/monitor/chrome) is mkDefault so a plain home.nix
assignment wins, and bind/exec-once lists concatenate. Full guide with
examples: docs/OVERRIDES.md.
| Option | Default | Purpose |
|---|---|---|
nomarchy.stateFile |
— (required) | Path to your theme-state.json |
nomarchy.terminal |
"ghostty" |
Terminal for keybinds and $TERMINAL |
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.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; toggle it on/off from the menu (System › Night light) or the Waybar moon/sun indicator |
nomarchy.updates.enable |
false |
Passive update awareness: a background check (.interval, default daily) that flags when flake inputs (nixpkgs, the Nomarchy input, …) are behind upstream — and, with Flatpak on, when apps have updates (.flatpak) — via a Waybar indicator + notification. Never changes anything; click the indicator to run the upgrade flow |
nomarchy.hyprland.enable |
true |
Nomarchy's Hyprland config |
nomarchy.waybar.enable |
true |
Nomarchy's Waybar |
nomarchy.rofi.enable |
true |
Themed rofi launcher + nomarchy-menu dispatcher |
nomarchy.swaync.enable |
true |
swaync notifications, themed |
nomarchy.idle.enable |
true |
hyprlock + hypridle (idle lock 5 min, display off 10, suspend 30) |
nomarchy.yazi.enable |
true |
yazi TUI file manager, themed + curated plugins |
nomarchy.osd.enable |
true |
swayosd on-screen display for volume/brightness/mute |
nomarchy.shell.enable |
true |
zsh + starship prompt + bat/eza/zoxide (zsh is the default login shell) |
nomarchy.ghostty.enable |
true |
Nomarchy's Ghostty |
nomarchy.btop.enable |
true |
btop with per-theme colors |
nomarchy.stylix.enable |
true |
GTK/Qt/cursor theming |
nomarchy.fastfetch.enable |
true |
fastfetch fronted by the themed Nomarchy logo |
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.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.greeter.enable |
true |
greetd/tuigreet |
nomarchy.system.greeter.autoLogin |
null |
Auto-login this user into Hyprland (installer sets it on LUKS machines) |
nomarchy.system.audio.enable |
true |
Pipewire stack |
nomarchy.system.bluetooth.enable |
true |
Bluetooth + blueman |
nomarchy.system.snapper.enable |
false |
Hourly/daily BTRFS timeline snapshots + nixos-rebuild-snap (installer enables it; no-op unless root is BTRFS) |
nomarchy.system.power.enable |
true |
Active power management (see below) |
nomarchy.system.power.backend |
"ppd" |
"ppd" (power-profiles-daemon + menu/Waybar switcher) or "tlp" (deeper battery tuning, no switcher) — mutually exclusive |
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.batteryChargeLimit |
null |
Stop charging at this % (e.g. 80) where the hardware supports it; needs power.laptop |
nomarchy.hardware.intel.enable |
false |
Intel enablement above nixos-hardware (GuC/HuC via i915.enable_guc=3); the installer sets it on an Intel CPU/GPU |
nomarchy.hardware.intel.computeRuntime |
false |
Opt-in: Intel GPU compute — OpenCL/Level-Zero (intel-compute-runtime) + oneVPL (vpl-gpu-rt) |
nomarchy.hardware.amd.enable |
false |
AMD enablement above nixos-hardware (amd-pstate EPP + radeonsi VA-API); installer-set on an AMD CPU/GPU |
nomarchy.hardware.amd.rocm.enable |
false |
Opt-in: ROCm HIP/OpenCL GPU compute (multi-GB); pair with .gfxOverride (e.g. "11.0.0") for an unlisted iGPU |
nomarchy.hardware.fingerprint.enable |
false |
fprintd for a detected fingerprint reader (installer-set); enroll with fprintd-enroll |
nomarchy.hardware.fingerprint.pam |
false |
Opt-in: use the fingerprint for login + sudo (PAM) |
nomarchy.hardware.npu.enable |
false |
Opt-in/experimental: load the on-die NPU driver (amdxdna/intel_vpu); userspace runtime is BYO |
nomarchy.hardware.latestKernel |
false |
Opt-in: ship linuxPackages_latest instead of the default kernel — for very new hardware whose drivers landed recently |
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
desktop services with lib.mkDefault (override natively). One worth
calling out: services.fwupd.enable is on by default for firmware
updates via LVFS — it only refreshes metadata, never flashes on its own,
so run fwupdmgr update to apply. Disable with services.fwupd.enable = false; on machines without real firmware (VMs/headless).
4. How theming works
Pure JSON ingestion
The trap with "read a mutable file from Nix" is pure evaluation: flakes
cannot read arbitrary $HOME paths without --impure (the old prototype
required it — never again). Nomarchy's convention: the state file lives
inside the consuming flake and is wired via
nomarchy.stateFile = ./theme-state.json;. Reading it is pure — it's flake
source. It must be git-tracked (nomarchy-theme-sync runs
git add --intent-to-add after every write as a safety net).
One change = one generation
nomarchy-theme-sync apply <theme> merges the preset into the JSON and runs
home-manager switch (override the command with $NOMARCHY_REBUILD, or pass
--no-switch to only write). Everything is baked: Hyprland, Waybar, Ghostty,
btop, and — via Stylix, mapped onto base16 roles — GTK, Qt, cursors and
fonts. No runtime patching means no partial states, and home-manager generations is also your theme history. Waybar even restyles in place: it
re-reads style.css when the symlink flips.
The wallpaper is the one runtime piece (awww — nixpkgs' swww — is
imperative; nothing in Nix consumes the path): applied at session start and
after every switch via a tiny activation hook, cycled instantly with
bg next.
Per-theme app assets (themes/<slug>/)
Recoloring covers 95% of theming; the rest is one optional assets directory per theme — a single place to look, unlike the old distro's split:
| Asset | Mechanism |
|---|---|
backgrounds/ |
wallpapers; empty wallpaper in the state means "first one"; bg next cycles |
btop.theme |
baked into the generation (generated from the palette when absent) |
waybar.css |
whole-swap: replaces the generated bar style entirely (probed at eval time, self-contained) |
waybar.jsonc |
whole-swap for the bar layout (must be plain JSON) |
rofi.rasi |
whole-swap: replaces the generated launcher/menu theme entirely |
Six ported themes ship a waybar.css identity (catppuccin, lumon, nord,
retro-82, summer-day, summer-night). Custom user themes can live in
$NOMARCHY_PATH/themes/ (preset lookup) and nomarchy.themesDir (eval-time
asset probe).
5. Day-to-day
nomarchy-theme-sync list # 21 presets (nord, gruvbox, rose-pine, …)
nomarchy-theme-sync apply kanagawa # whole desktop, one generation (~a switch)
nomarchy-theme-sync set ui.gapsOut 16 # tweak one knob (also a switch)
nomarchy-theme-sync bg next # cycle wallpapers — instant, no rebuild
nomarchy-theme-sync bg auto # back to the theme's default wallpaper
nomarchy-theme-sync get colors.accent
sys-update # update inputs + rebuild the system (snapshots first)
home-update # rebuild just the desktop layer
Keybinds: SUPER+Return terminal · SUPER+D launcher · SUPER+T theme
picker · SUPER+SHIFT+T next wallpaper · SUPER+X power menu ·
SUPER+E file manager (yazi) · SUPER+N notifications · SUPER+CTRL+V
clipboard history · SUPER+Q
close · SUPER+1..9 workspaces · Print region screenshot.
Shell aliases (zsh, gated on nomarchy.shell.enable) — alias lists them
all; the curated set:
# 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
- New theme: drop a JSON into
themes/(schema = any existing preset), plus an optionalthemes/<slug>/assets directory. - New themed value: add the key to
theme-state.jsonand consume it in the Nix modules. One place — there is no second renderer to keep in sync. - Importing more old-distro palettes:
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 intemplates/downstream/home.nixorsystem.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.packageslist in yourhome.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. Nonomarchy.apps.*toggles — a package list is already its own opt-out, so the distro doesn't impose these or wrap them.
Roadmap & known issues
See docs/ROADMAP.md — forward-looking plans plus the log of shipped fixes. Kept out of the README so this stays a focused entry point.