# 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: .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 │ └── 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: ```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). ## 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 github:YOUR-USER/nomarchy ``` You own three files: `system.nix`, `home.nix`, and **your own `theme-state.json`**. 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 ``` 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 ` 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//`) 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 ```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 ``` 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//` 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 - **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