Bernardo Magri f211ef0d09 feat: Nomarchy ground-up rewrite on NixOS 26.05
Full replacement of the previous iteration, rebuilt around three ideas:

- Pure evaluation: theme-state.json lives inside the flake and is read
  via the nomarchy.stateFile option — no --impure, ever.
- All-Home-Manager theming: `nomarchy-theme-sync apply` writes the JSON
  and runs `home-manager switch`; every theme change is one atomic,
  rollbackable generation. Wallpaper (swww) is the sole runtime piece.
- Flat, downstream-first layout: modules/{nixos,home} with one
  options.nix each, exported as nixosModules/homeModules + overlay +
  flake template; system (nixos-rebuild) and desktop (home-manager
  switch) rebuild paths are fully split.

Ships 21 themes imported from the previous iteration (palettes,
wallpapers, btop themes, six whole-swap Waybar identities), Stylix for
the GTK/Qt/cursor long tail, a live ISO target with offline theme
switching, and docs/TESTING.md with the QEMU verification workflow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 10:59:13 +01:00

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 swww (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)
├── 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
│   └── 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
├── 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)
├── templates/downstream/          # `nix flake init -t` starter for users
├── docs/TESTING.md                # how to verify changes (incl. AI-agent rules)
└── tools/                         # maintainer-only
    ├── import-palettes.py         #   converts old-distro themes → JSON + assets
    └── test-live-iso.sh           #   build the ISO + boot it in QEMU

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.

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 github:YOUR-USER/nomarchy

You own three files: system.nix, home.nix, and your own theme-state.json. Two deliberately separate rebuild paths:

sudo nixos-rebuild switch --flake .#default   # system: rare
home-manager switch --flake .#me              # desktop: every theme change, no sudo

Override anything with plain NixOS/HM options (the distro uses mkDefault throughout) or the nomarchy.* surface:

Option Default Purpose
nomarchy.stateFile — (required) Path to your theme-state.json
nomarchy.terminal "ghostty" Terminal for keybinds and $TERMINAL
nomarchy.hyprland.enable true Nomarchy's Hyprland config
nomarchy.waybar.enable true Nomarchy's Waybar
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.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 <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 (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)

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

Keybinds: SUPER+Return terminal · SUPER+D launcher · SUPER+T theme picker · SUPER+SHIFT+T next wallpaper · 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/<slug>/ 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 <palettes-dir> themes/.

Roadmap

  • 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
  • Plymouth + SDDM/greeter theming from the same JSON
  • Interactive installer on the live ISO (disko BTRFS+LUKS, hardware profiles) — port from old_distro
  • hyprlock/hypridle, swayosd, launch-or-focus UX scripts
Description
NixOS based distribution with Omarchy flavour
Readme MIT 308 MiB
Languages
Nix 71.4%
Python 11.9%
Shell 11.8%
CSS 4.9%