Bernardo Magri 995810927d fix(install): pin HOME=/root so root nix never strays into /home/nomarchy
`exec sudo --preserve-env` (needed to carry the NOMARCHY_* vars) also
drags the live session user's HOME=/home/nomarchy into the root install
process. Root-run `nix build` then writes an eval cache + .nix-defexpr
there, and the in-chroot one lands on the TARGET as a stray, orphaned
/home/nomarchy (no such user on the installed system). Pin root's own
HOME after the sudo re-exec, and again inside the nixos-enter chroot
script. The user activation already sets HOME=/home/$USERNAME and is
unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:21 +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 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 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/.
  • 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 in templates/downstream/home.nix or system.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.packages list in your home.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. No nomarchy.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.

Description
NixOS based distribution with Omarchy flavour
Readme MIT 308 MiB
Languages
Nix 71.4%
Python 11.9%
Shell 11.8%
CSS 4.9%