Theme-switch progress feedback; per-theme icon themes (unblocks rofi show-icons); starship prompt + modern-CLI aliases (bat/eza/rg/fd/zoxide); opt-out default app suite (nomarchy.apps.*); Plymouth logo contrast fix; hibernate double-unlock (hyprlock redundant after LUKS resume); plus a keybindings-cheatsheet menu module and the SUPER+Space / SUPER+M binds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
341 lines
19 KiB
Markdown
341 lines
19 KiB
Markdown
# 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
|
|
│ └── 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
|
|
├── 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.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 <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
|
|
|
|
```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/<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
|
|
- **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/<slug>/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`.
|
|
- next modules: Network ▸ nmtui · Bluetooth ▸ blueman · Capture ▸
|
|
screenshot · **keybindings cheatsheet** (parse the bind list → rofi,
|
|
so SUPER-? shows every shortcut) · **ask Claude**: free-text →
|
|
`$TERMINAL -e claude "<question>"` — the claude CLI auths via OAuth
|
|
against a Pro/Max subscription (no API key); REPL stays open for
|
|
follow-ups (claude-code in nixpkgs is unfree — allowUnfree is on)
|
|
- planned binds: `SUPER+Space` → `rofi -dmenu`/quick-launch and
|
|
`SUPER+M` → `nomarchy-menu` (main menu); `SUPER+D` stays `-show drun`
|
|
- launcher icons: `show-icons` is off until the icon-theme item below
|
|
lands (rofi needs an installed icon theme to show app icons)
|
|
- 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/<slug>/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`)
|
|
- swayosd (volume/brightness OSD), launch-or-focus UX scripts
|
|
- **Distro branding, round 2:** `distroName = "Nomarchy"` ships
|
|
(os-release `PRETTY_NAME`, systemd-boot entries, ISO menu label).
|
|
Remaining: `isoImage.splashImage`/`grubTheme` art for the ISO boot
|
|
screen, tuigreet/MOTD text, and the `distroId` question (it changes
|
|
`DEFAULT_HOSTNAME` and upstream `isNixos` checks — needs a test pass;
|
|
nixos-* CLI names stay regardless)
|
|
- **Theme-switch feedback:** a switch runs `home-manager switch` (seconds
|
|
to minutes), and today the only signal is the notify-send toast
|
|
("Applying theme — rebuilding…"). Add a visible progress indicator so a
|
|
slow switch never reads as a failed selection — e.g. a spinner/progress
|
|
widget from `nomarchy-theme-sync` (it already brackets the rebuild) or a
|
|
Waybar/swaync element while the switch runs.
|
|
- **Icon themes:** add `icon` to `theme-state.json`, have Stylix/GTK and
|
|
rofi (`show-icons`) consume it, and ensure the chosen icon theme is
|
|
pulled into the closure per active theme (a dark/light pair, e.g.
|
|
Papirus-Dark/Light, picked to match `mode`). Unblocks rofi launcher
|
|
icons and tidies Thunar/GTK app icons.
|
|
- **Nicer shell out of the box:** ship and default-configure a prompt
|
|
(starship, themed from the JSON) plus modern-CLI ergonomics —
|
|
`bat`→cat, `eza`→ls, `ripgrep`→grep, `fd`→find, `zoxide`→cd — wired as
|
|
aliases/`$PAGER` so the terminal is pleasant by default (overridable).
|
|
- **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 splash logo is too dark against
|
|
the dark `base` background; recolor/lighten it (or tint from the palette
|
|
`accent`/`text` instead of shipping a fixed navy asset) so it reads on
|
|
both light and dark themes.
|
|
- **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`.
|