# 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: .json + optional / 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 │ └── 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 (power, clip) │ ├── 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 ├── 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 └── 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: ```sh 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](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: ```sh 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: ```nix 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](https://github.com/NixOS/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: ```sh 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: ```sh sys-update # nix flake update + system rebuild (BTRFS snapshot first when available) home-update # home-manager switch (no flake update, no sudo) ``` 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](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 it; pairs with xkb + `console.useXkbConfig` in system.nix) | | `nomarchy.keyboard.variant` | `""` | XKB variant for the session | | `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.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.audio.enable` | `true` | Pipewire stack | | `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman | ## 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 ` 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//`) 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 ```sh 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. ## 6. Extending - **New theme:** drop a JSON into `themes/` (schema = any existing preset), plus an optional `themes//` assets directory. - **New themed value:** add the key to `theme-state.json` and 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 themes/`. ## Roadmap - **Menu system** (apps launcher + theme switching + system actions), built on rofi 2.0 (native Wayland on 26.05) — its `.rasi` theme is baked from theme-state.json like every other app, with rich per-element styling: - ✓ shipped: `modules/home/rofi.nix` (per-element theme generated from the palette, `themes//rofi.rasi` whole-swap) and the `nomarchy-menu` dispatcher: root picker (no args) · `power` (lock/logout/suspend/hibernate/reboot/shutdown, SUPER+X) · `theme` (SUPER+T) · `clipboard` (cliphist, SUPER+CTRL+V) · `calc` (qalc, copy/chain) · `files` (fd → xdg-open) · `web` (DuckDuckGo). SUPER+D is `rofi -show drun`. - ✓ shipped modules: `network` (nmtui in `$TERMINAL`) · `bluetooth` (blueman-manager) · `capture` (grim/slurp submenu: region/full → clipboard/file, saved to `~/Pictures/Screenshots`) · `keybinds` (the cheatsheet, see below) · `ask` (free-text → claude CLI in a terminal; auths via OAuth, no API key; pulled fresh via `npx @anthropic-ai/claude-code@latest` rather than the nixpkgs package, which lags model releases — `nodejs` is bundled for npx; REPL stays open) - ✓ keybindings cheatsheet: `modules/home/keybinds.nix` is the **single source** for both the Hyprland binds and the SUPER+? rofi list, so they can't drift; `nomarchy-menu keybinds` renders the padded two-column sheet (generated/mouse binds carried in its `extra` rows) - ✓ shipped binds: `SUPER+Space` → `rofi -show drun` (quick launch) · `SUPER+M` → `nomarchy-menu` (main menu) · `SUPER+?` → the cheatsheet; `SUPER+D` stays `-show drun` - launcher icons: ✓ `show-icons` on, drawing from the theme's icon set (Papirus, via the icon-themes work below) - decision record: resolves the old Walker/Lua question — no GTK4 launcher, no second theming pipeline; the dispatcher owns the menu structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0 once mainline gained native Wayland, for its richer theming) - **Theme parity with legacy:** summer-day/night now carry their legacy bar layouts as `waybar.jsonc` whole-swaps (adapted: dead legacy script modules dropped, Nerd-Fonts-v2 codepoints remapped to FontAwesome/v3, logo button opens nomarchy-menu); the other four identity themes are palette recolors and already match. Remaining: a visual pass over all six on the live ISO - **Per-theme rofi identity:** the `themes//rofi.rasi` whole-swap ships, and summer-day/night carry their legacy designs (inverted window, green inputbar, yellow bottom-border). Remaining: author `.rasi` identities for the other four ported themes if/when they want one (the generated palette theme is the default and looks fine) - **Faster switches:** move `backgrounds/` out of the flake source (the 86 MB re-copy on every state write is the main eval tax), then pre-built theme variants if still needed - Greeter (tuigreet/SDDM) theming from the same JSON (Plymouth ships since v1: `nomarchy.system.plymouth.*`, background tinted from the state file) - Installer round 2: multi-disk BTRFS RAID, impermanence, BIOS/legacy boot (v1 `nomarchy-install` is single-disk UEFI — see `pkgs/nomarchy-install`) - launch-or-focus UX scripts (swayosd volume/brightness OSD ships since v1: `nomarchy.osd.*`, media keys drive `swayosd-client`, themed from the JSON) - **Distro branding, round 2:** `distroName = "Nomarchy"` ships (os-release `PRETTY_NAME`, systemd-boot entries, ISO menu label). ✓ tuigreet greeting (`Welcome to `) and a branded `users.motd` (doubling as a helper cheat sheet), both keyed off `distroName`. Remaining: `isoImage.splashImage`/`grubTheme` art for the ISO boot screen (needs a designed asset), and the `distroId` question (it changes `DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass; nixos-* CLI names stay regardless) - **Theme-switch feedback:** ✓ the "rebuilding…" notification is now persistent (timeout 0) and replaced in place by "applied ✓" / failure via a synchronous tag, so a multi-minute switch never reads as a failed selection. (An honest indeterminate indicator — HM gives no % — a literal progress widget would be cosmetic; revisit only if wanted.) - **Icon themes:** ✓ ships `papirus-icon-theme`; the resolved name lives on `nomarchy.theme.iconTheme` (the JSON's optional `icons` field, else Papirus-Dark/Light by `mode`) and feeds both Stylix's `gtk.iconTheme` (Thunar/GTK apps) and rofi's `show-icons`. Remaining (optional): per-theme `icons` overrides for the presets, or shipping more icon packs. - **Nicer shell out of the box:** ✓ zsh is the default login shell, with a starship prompt themed from the JSON, autosuggestions + syntax highlighting, and modern-CLI ergonomics — `cat`→bat (theme "ansi", so it tracks the palette), `ls`→eza, `cd`→zoxide, plus `lt`/`tree`. `nomarchy.shell.enable`. (Deliberately did NOT alias grep→rg / find→fd: their flags differ enough to surprise; `rg`/`fd` ship as themselves.) - **Default application suite:** install a complete-workstation set (vscode, libreoffice, gimp, inkscape, texlive-full, …) behind a `nomarchy.apps.*` option surface so each is individually opt-out for users who want a leaner machine. Watch closure size (texlive-full is multi-GB — likely default-off or a lighter scheme). - ✓ **Plymouth logo contrast:** the shipped art was a fixed navy that vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every element from the palette at build time (flat fill, alpha kept): logo/lock/bullet → text, entry/progress-box → surface, progress-bar → accent. Reads on light and dark; follows the theme as of the last system rebuild (like the background tint). - **Hibernate double-unlock:** on resume from hibernate the LUKS passphrase already gates the machine, but hypridle's `before_sleep_cmd` also locks hyprlock, so the user types a password twice. Lock only on suspend (not hibernate), or skip the hyprlock prompt when resuming from a LUKS-encrypted hibernate — see `modules/home/idle.nix`. - ✓ **Key agents & pinentry:** ships `modules/home/keys.nix` (`nomarchy.keys.enable`): one agent — `services.gpg-agent` with `enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland, Stylix-themed Qt) handles GPG and SSH passphrases alike. `SSH_AUTH_SOCK` comes from the agent's zsh integration; cache TTLs (30 min / 2 h) govern re-prompting. gnome-keyring stays the Secret Service (modern versions run no SSH agent, so no socket contention); screen lock doesn't flush the cache. Remaining (optional): a session-level `SSH_AUTH_SOCK` export so GUI clients launched outside a shell also see the agent. ## Known issues & follow-ups - ✓ **Yazi TOML parse error on startup:** yazi 26.x made fetchers' `group` field required, so `[[plugin.prepend_fetchers]]` failed to parse and yazi fell back to presets. Fixed by adding `group = "git"` to both git fetcher entries in `modules/home/yazi.nix` (the git plugin's `setup()` only renders the linemode; the fetcher still needs registering). - ✓ **Starship prompt styling:** dropped the powerline look (the `[]` separator glyph + `bg:` fills) for a flat prompt — bold-accent directory, warn git branch, subtext status/duration, `❯` character, all plain colored text (`modules/home/shell.nix`). Also wired in `cmd_duration`, which was configured but never referenced in `format`. - ✓ **xfce deprecation warnings:** moved `xfce.exo` → `pkgs.xfce4-exo` and the three Thunar plugins to their new top-level names in `modules/nixos/file-manager.nix`; the eval warnings are gone. - ✓ **Rofi function keybindings:** direct `SUPER+CTRL+` binds jump straight to each `nomarchy-menu` module — V clipboard · C calc · W web · F files · N network · B bluetooth · S capture · A ask — added to `modules/home/keybinds.nix`, so they also show in the SUPER+? cheatsheet. - ✓ **Enable nix-ld by default:** `programs.nix-ld.enable` is on distro-wide (`modules/nixos/default.nix`), so prebuilt/foreign dynamically-linked binaries run out of the box. - ✓ **Hyprland border colors off for some themes:** the cause was v1 forcing an `accent→accentAlt` gradient on every theme; legacy used a *solid* border (accent for nord/retro-82/lumon, the text tone for kanagawa/summer-day/summer-night). Added a palette-resolved `border` field (`{active, inactive}`) to the theme schema (`theme.nix`), consumed solid in `hyprland.nix`, and declared it in every preset so a switch always replaces it. All six identity themes now match legacy exactly. - ✓ **Waybar shows non-existent workspaces:** the v1 summer port "fixed" legacy's deprecated `persistent_workspaces` (underscore — silently ignored by current Waybar, so legacy only ever showed existing workspaces) into the modern `persistent-workspaces` (hyphen), which Waybar honours → all 10 rendered as phantoms. Dropped the block from `themes/summer-{day,night}/waybar.jsonc`; the other themes use the generated `waybar.nix`, which never had it, so they already match. - **GTK/Qt ignore the theme's light/dark mode:** `stylix.nix` sets `polarity` from `theme.mode`, but GTK4/libadwaita apps (and Qt via the portal) decide dark vs light from the `org.freedesktop.appearance color-scheme` / gsettings `org.gnome.desktop.interface color-scheme` key, which Stylix doesn't write — so a light theme can still render apps dark (and vice-versa). Fix: set `dconf` `color-scheme` = `prefer-dark`/`prefer-light` from `t.mode` (and confirm the xdg-desktop-portal exposes it), so libadwaita/Qt follow the palette.