All checks were successful
Check / eval (push) Successful in 3m22s
BACKLOG #103. Bernardo, live ISO 2026-07-14: still no browser, no office — "that makes a live iso useful". He is right, and it is the thing a user judges the distro by before installing, and what they boot to rescue a machine that won't start. It could do neither. The live HM user now names its own set: chromium, libreoffice-fresh, gnome-text-editor, amberol, snapshot. Firefox deliberately excluded (Bernardo's call): chromium already owns the HTTP mime default and ships in the installed template, so it is the browser that matches what a user gets post-install. I asked him to choose on a "~+2 GB" premise. The premise was wrong, and measuring rather than assuming is what corrected it: system.extraDependencies ALREADY pins the template's HM closure into the ISO for offline installs, so chromium/libreoffice/amberol were already in the image's store — merely absent from the live user's profile, so nothing put them on PATH or in the launcher. The live and template chromium resolve to the SAME store path, verified, so reusing the template's exact `chromium.override { enableWideVine = true; }` costs zero; a plain `chromium` would have been a second 2.5 GiB closure. Only gnome-text-editor and snapshot are new: 9 paths, 133 MiB uncompressed. His Firefox call still held for the right reason — it was the one item genuinely unpinned. V2. Measured ISO delta, both built from the same tree — the item's pass condition: 8.038 → 8.078 GiB = +41.2 MiB (+0.50%). New checks.live-baseline-apps asserts each app is on PATH *and* has a .desktop the launcher can see (a binary without one is invisible, which is the failure that matters), that HTTPS resolves to a chromium entry actually PRESENT (#94's exact trap — a mime default naming a package nothing ships), and that firefox has not crept back, since that is a size decision not a drive-by. The guard was proved to fail: dropping snapshot makes it name the missing entry. flake check, live-install-entry, option-docs, template-sot green. V3 pending: that the apps LAUNCH needs hardware — queued on the Acer M5-481T, from the launcher only, since that is the claim a file-level check cannot make. Also filed, from measuring the 8 GiB rather than speculating about it: * #120 (NEXT, Bernardo's call) — a netinstall ISO beside the offline one. Starts from numbers: the offline pin is only 4.02 of 18.03 GiB uncompressed (~22%), so dropping it still leaves a 14 GiB desktop (~6.3 GiB compressed). "No pin" is not the lighter ISO; and without a binary cache for Nomarchy's own outputs a netinstall trades a download for a from-source install. * #121 — the Widevine wrapper drags a SECOND 687 MiB unwrapped chromium in for its share/applications alone. Pre-existing, on every installed machine. * #119 — text/plain names vscode, which the live ISO never ships. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
514 lines
36 KiB
Markdown
514 lines
36 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/ # 24 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
|
||
│ ├── battery-notify.nix # low-battery toasts at the bar's thresholds
|
||
│ ├── 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)
|
||
│ ├── nomarchy-doctor/ # read-only health sheet, one command per failure
|
||
│ ├── nomarchy-control-center/ # TUI settings front-end over nomarchy-theme-sync (gum)
|
||
│ └── nomarchy-battery-notify/ # low-battery toast watcher backing battery-notify.nix
|
||
├── templates/downstream/ # machine flake SoT (`flake init` + installer copy/patch)
|
||
├── docs/ # human docs — map: docs/README.md
|
||
│ ├── VISION.md # product themes toward v1.0 (not a queue)
|
||
│ ├── ROADMAP.md # design/decision records + shipped log
|
||
│ ├── HARDWARE.md # firmware, profiles, drivers
|
||
│ ├── TESTING.md · RECOVERY.md · OVERRIDES.md · MIGRATION.md
|
||
├── agent/ # agent instructions + loop state — map: agent/README.md
|
||
│ # BACKLOG (executable queue), LOOP, VERIFICATION, …
|
||
├── AGENTS.md # agent entry, any vendor (CLAUDE.md symlinks here)
|
||
├── .claude/ # Claude Code adapter: permissions + subagent defs
|
||
└── 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).
|
||
|
||
It's a working desktop, not a demo shell: Chromium, LibreOffice, Text Editor,
|
||
Amberol and Snapshot are in the launcher alongside the terminal tooling — so
|
||
you can read these docs in a browser while you try it, or boot the stick to
|
||
rescue a machine that won't start and get a document off it. Installed
|
||
machines get their app list from the template's `home.packages` instead, which
|
||
is yours to edit.
|
||
|
||
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 — see **[docs/HARDWARE.md](docs/HARDWARE.md)**),
|
||
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"
|
||
```
|
||
|
||
The template's own README then walks the first-run steps: a real
|
||
`hardware-configuration.nix`, `git init` (flakes only see tracked
|
||
files), the two first rebuilds, and landing the checkout at
|
||
`~/.nomarchy` so the theme CLI finds it. (The installer does all of
|
||
this for you.)
|
||
|
||
Already running NixOS on this machine? No reinstall needed — adopt
|
||
Nomarchy in place, reusing your existing `hardware-configuration.nix`:
|
||
**[docs/MIGRATION.md](docs/MIGRATION.md)**.
|
||
|
||
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
|
||
nomarchy-pull # nix flake update — refresh inputs (nomarchy, nixpkgs, …);
|
||
# optional git pull only if ~/.nomarchy tracks a remote
|
||
nomarchy-rebuild # system rebuild against the CURRENT lock (sudo inside;
|
||
# BTRFS snapshot first when snapper is on; nvd diff after)
|
||
nomarchy-home # home-manager switch — desktop only, no sudo, no lock bump
|
||
# (prints nvd + a "What changed" toast when the generation moved)
|
||
nomarchy-what-changed # plain-language last rebuild (system + desktop; --summary for one-liners)
|
||
```
|
||
|
||
`~/.nomarchy` is **your machine flake** (`system.nix` / `home.nix`). Newer
|
||
Nomarchy code arrives when `flake.lock` updates the `nomarchy` input
|
||
(`nomarchy-pull`), not by `git pull` of that directory (many installs have
|
||
no remote there — only local commits from auto-commit). With auto-commit
|
||
on (menu: **System › Auto-commit**), all three commands above also sweep
|
||
any pending hand edits into a commit before they switch, so
|
||
`git -C ~/.nomarchy log` mirrors your generations.
|
||
|
||
| When | Run |
|
||
|---|---|
|
||
| Pull newer Nomarchy / nixpkgs / inputs | `nomarchy-pull` |
|
||
| You changed `system.nix` (or after pull) | `nomarchy-rebuild` |
|
||
| You changed `home.nix` / theme / desktop | `nomarchy-home` |
|
||
| Full upgrade (inputs + both layers) | `nomarchy-pull && nomarchy-rebuild && nomarchy-home` |
|
||
|
||
**Order matters for distro updates.** `nomarchy-home` rebuilds against the
|
||
**current** `flake.lock` — it never updates inputs. A new Nomarchy revision
|
||
lands only when the lock is updated (`nomarchy-pull`). So for an upstream
|
||
desktop change: **pull → rebuild → home**. Home before pull rebuilds against
|
||
the old inputs and silently skips new home-side changes. After a home-side
|
||
keybind/config change, also `hyprctl reload` (or relogin) so the session
|
||
re-reads it.
|
||
|
||
Legacy aliases still work: `sys-update` → pull+rebuild, `sys-rebuild` →
|
||
`nomarchy-rebuild`, `home-update` → `nomarchy-home`.
|
||
|
||
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)**.
|
||
|
||
**Where each option goes.** `nomarchy.system.*`, `nomarchy.hardware.*`, and
|
||
`nomarchy.services.*` are NixOS options — set them in `system.nix`. Everything
|
||
else under `nomarchy.*` is a Home Manager option — set it in `home.nix`. The
|
||
two tables below are split along exactly that line.
|
||
|
||
**`home.nix`** (Home Manager — the desktop):
|
||
|
||
| 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` | `[]` | Optional layouts to put first in the new-keyboard picker; the watcher runs by default, offers every installed XKB layout, and remembers the choice per-device. Pick/change one anytime at System › Keyboard |
|
||
| `nomarchy.nightlight.enable` | `false` | Scheduled blue-light filter (hyprsunset) — warm at night (`.temperature`, default 4000K) between `.sunset`/`.sunrise`, no shift by day; set BOTH `.latitude`/`.longitude` for **geo mode** (wlsunset computes sunrise/sunset from your location; the fixed times are ignored); off by default — enable it from the menu (System › Night light; the first enable rebuilds), then toggle on/off instantly from the menu or the Waybar moon indicator (writes `settings.nightlight.on` in your flake, no rebuild) and it survives reboot, so it stays reproducible |
|
||
| `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.batteryNotify.enable` | `true` | Low-battery toasts at the bar's thresholds — 25% low, 10% critical (stays up until dismissed); silent no-op on machines without a battery |
|
||
| `nomarchy.dockAudio.enable` | `true` | On a fresh external-monitor plug, PipeWire/WirePlumber are reprobed after hardware settles and the highest-priority available HDMI/DP/USB sink becomes default, with a toast and journal result. A later manual speaker choice sticks until the next physical plug; unplug falls back to built-in. Manual route: System › Audio |
|
||
| `nomarchy.firstBootWelcome.enable` | `true` | One dismissible “you're set” toast on the first session (SUPER+M / SUPER+T / SUPER+? + network pointer); marker is `settings.firstBootShown` in the flake checkout |
|
||
| `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 15 min — battery-only) |
|
||
| `nomarchy.idle.fingerprint` | `false` | Unlock the lock screen with a fingerprint as well as the password, and say so on the input field. Set it alongside `nomarchy.hardware.fingerprint.pam` in system.nix: hyprlock is configured from Home Manager, which cannot read the NixOS option — and hyprlock does **not** take a fingerprint through PAM at all (its PAM stack runs only on submit), so it uses its own fprintd backend that this switch turns on |
|
||
| `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.viewers.enable` | `true` | Document/image viewers: zathura (Stylix-themed PDF) + imv |
|
||
| `nomarchy.mime.enable` | `true` | Default "open with" associations (PDF/image/video/text/browser/directory); entries for apps you removed are skipped, so it degrades with the suite |
|
||
| `nomarchy.monitors` | `[]` | Declarative per-output layout → Hyprland `monitor` rules (applied on hotplug); `,preferred,auto,1` wildcard kept as fallback |
|
||
| `nomarchy.displayProfiles` | `{}` | Named layouts for the same outputs (docked/undocked/…), switched from System › Display › Profiles: instant via hyprctl, persisted in-flake (`settings.displayProfile`), baked over `nomarchy.monitors` at the next rebuild. Profiles that disable the laptop panel use the same safe ordering as Dock mode (external on → workspace handoff → internal off). The menu's Auto-switch row (`settings.displayProfileAuto`) applies the matching profile from Hyprland hotplug events. Workspace pins (`workspaces = { "1" = "DP-3"; }`) are moved instantly and baked as rules |
|
||
| `nomarchy.launchOrFocus` | `[]` | Launch-or-focus binds: `SUPER+<key>` focuses the app's window (case-insensitive class match) or launches it; entries land in the SUPER+? cheatsheet, and a bind whose app was removed notifies instead of failing silently |
|
||
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
|
||
| `nomarchy.package` | overlay's `nomarchy-theme-sync` | The theme/state tool package, overridable if you fork it |
|
||
|
||
**Always-on, no toggle by design:** `services.cliphist`, `services.udiskie`
|
||
(automount + safe-removal toasts) and `services.easyeffects` (mic noise
|
||
cancellation) ship unconditionally — small, low-risk pieces with no
|
||
Nomarchy-specific config behind them (the toggle-vs-package rule in
|
||
`agent/CONVENTIONS.md`). Disable one from `home.nix` with the plain HM
|
||
option, e.g. `services.easyeffects.enable = lib.mkForce false;` — see
|
||
**[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
||
|
||
**`system.nix`** (NixOS — the machine):
|
||
|
||
| Option | Default | Purpose |
|
||
|---|---|---|
|
||
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
|
||
| `nomarchy.system.stateFile` | `null` | theme-state.json for the system-side consumers (the Plymouth tint); `lib.mkFlake` wires it for you — set it only when composing the modules by hand |
|
||
| `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` | state | Auto-login this user into Hyprland. Owned by System › Auto-login (`settings.greeter.autoLogin`; installer seeds it on LUKS machines) — set it here only to pin it against the menu |
|
||
| `nomarchy.system.audio.enable` | `true` | PipeWire stack; WirePlumber priority rules provide HDMI/USB preference/fallback, while the Home Manager dock watcher performs the fresh-display reprobe and explicit default selection |
|
||
| `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman |
|
||
| `nomarchy.system.autoTimezone.enable` | `false` | Automatic timezone (geoclue + automatic-timezoned) — the clock follows your location; toggle from System › Auto timezone (a menu enable rebuilds: it has to unset the static `time.timeZone`) |
|
||
| `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. Clamshell uses logind's `HandleLidSwitchDocked=ignore`; the desktop additionally holds a low-level lid-switch inhibitor across dock/undock, releasing only after the internal panel is restored and the lid opens. Normal future undocked lid-close suspend remains enabled |
|
||
| `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 firmware via `i915.enable_guc=3` — `.guc` toggles just that; the installer unsets it on `xe`-driver GPUs); 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, each toggleable via `.pstate` / `.vaapi`); 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.fingerprint.parallel` | `true` | With PAM on: password *or* fingerprint at the same prompt (whichever comes first); `false` = stock sequential pam_fprintd. Password alone always stays sufficient |
|
||
| `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.hardware.camera.hideIrSensor` | `false` | Hide a dual-sensor webcam's IR node from PipeWire's **v4l2** path so apps only ever see the colour camera (the "second, dark Integrated Camera"); installer-set on a paired RGB+IR webcam. `/dev/video*` stays open, so Howdy-style face unlock still works; `.irMatch` overrides the IR-name regex. Does **not** hide IR from libcamera / portal / Flatpak pickers — see [HARDWARE.md §7](docs/HARDWARE.md) |
|
||
| `nomarchy.hardware.i2c.enable` | `false` | I2C devices support — access to `/dev/i2c-*` (RGB controllers, sensors, DDC/CI monitor control) |
|
||
| `nomarchy.hardware.i2c.ddcci` | `false` (distro default: **`true`**) | the ddcci-driver kernel module, exposing external monitors as standard backlight devices via DDC/CI so brightness keys and swayosd natively control them |
|
||
| `nomarchy.services.tailscale.enable` | `false` | Opt-in: Tailscale mesh VPN — the login user is made the operator, so `tailscale up/down/set` and the System › VPN menu work without sudo |
|
||
| `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`; backs up `.paths`, default `/home`; 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) and enforces
|
||
baseline security/stability defaults. A few worth calling out:
|
||
**`security.apparmor.enable`** is on by default for mandatory access
|
||
control confinement. **Kernel panics** are configured to auto-reboot the
|
||
machine after 10 seconds (`panic=10 oops=panic`) rather than hanging
|
||
indefinitely. **`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). Full hardware
|
||
story (profiles, fingerprint, NVIDIA, unsupported machines, contributing
|
||
a DMI line): **[docs/HARDWARE.md](docs/HARDWARE.md)**. And
|
||
**`services.earlyoom`** is on by default so running out of memory kills
|
||
the offending process (with a desktop notification) instead of freezing
|
||
the desktop — process-level on purpose, since a Hyprland session is one
|
||
cgroup and systemd-oomd would kill all of it (oomd is disabled
|
||
accordingly). Opt out with `services.earlyoom.enable = false;`.
|
||
**`zramSwap`** is also on by default (zstd, 50% of RAM, priority 100):
|
||
day-to-day memory pressure pages into compressed RAM instead of the
|
||
disk, keeping the disk swapfile free for **hibernation** — the installer
|
||
creates a RAM-sized encrypted swapfile and wires `resume`, so
|
||
Power › Hibernate works out of the box (swap size 0 at install = no
|
||
swap). Opt out with `zramSwap.enable = false;`; to add hibernation to a
|
||
machine installed before it existed, follow
|
||
**[docs/MIGRATION.md](docs/MIGRATION.md)** § Enabling hibernation.
|
||
|
||
## 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,
|
||
after every switch via a tiny activation hook, and again when a monitor is
|
||
hotplugged; cycled instantly with `bg next`.
|
||
|
||
### Config the menu writes (not just themes)
|
||
|
||
The in-flake-state model isn't only for appearance. **Feature toggles you flip
|
||
from the menu are written into a `settings.*` section of the *same* state file**
|
||
— git-tracked, reproducible, never stashed in `~/.local/state`. The menu is just
|
||
an ergonomic writer for your flake, so version-controlling your downstream
|
||
reproduces the machine, settings and all. Where a toggle can take effect without
|
||
a rebuild it does: the menu writes the key (`--no-switch`) and flips the running
|
||
service, which reads the *live* flake state at session start, so the choice is
|
||
both instant and survives reboot.
|
||
|
||
**Night light** is the first to use this — enable it from the menu (System ›
|
||
Night light; the first enable rebuilds to install hyprsunset), then on/off is
|
||
instant. Internally it's two keys: `settings.nightlight.installed` (sticky —
|
||
gates the unit, the first enable rebuilds) and `settings.nightlight.on` (the
|
||
instant on/off). Expect more `nomarchy.*` toggles to migrate to this pattern.
|
||
|
||
**Auto-commit (opt-in):** System › Auto-commit makes every `apply`/`set`
|
||
mutation also `git commit` theme-state.json in your flake — *only* that
|
||
file, so unrelated dirty work is never swept up — turning your settings
|
||
history into `git log`. Off by default; the toggle is instant (nothing in
|
||
Nix consumes the flag), the off-write is itself committed so history stays
|
||
consistent, and wallpaper cycling (`bg next`) is deliberately excluded —
|
||
the current wallpaper rides along with the next real commit.
|
||
|
||
### 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 |
|
||
|
||
Four themes ship a `waybar.css` identity (summer-day, summer-night,
|
||
executive-slate, boreal). 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 # 24 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
|
||
nomarchy-pull # flake input update (no rebuild)
|
||
nomarchy-rebuild # rebuild the system, current lock
|
||
nomarchy-home # rebuild just the desktop layer
|
||
nomarchy-doctor # read-only health sheet (also: menu › System › Doctor)
|
||
```
|
||
|
||
Something broke anyway? Every rebuild is a generation and (on BTRFS)
|
||
every hour is a snapshot — the undo story, from a bad theme to a
|
||
machine that won't boot, is **[docs/RECOVERY.md](docs/RECOVERY.md)**.
|
||
|
||
Keybinds: `SUPER+Return` terminal · `SUPER+Space` 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+SHIFT+C` color picker (hyprpicker) · `SUPER+Q`
|
||
close · `SUPER+1..9` workspaces · `SUPER+ALT+arrow` move workspace to monitor · `Print` region screenshot.
|
||
|
||
Shell aliases (zsh, gated on `nomarchy.shell.enable`) — `alias` lists them
|
||
all; the curated set:
|
||
|
||
```sh
|
||
# 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 (lifecycle: nomarchy-pull / nomarchy-rebuild / nomarchy-home — full names)
|
||
ns nr # nix shell · nix run (e.g. ns nixpkgs#ripgrep)
|
||
nfu nfc # nix flake update · check (prefer nomarchy-pull for day-to-day)
|
||
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/` (roles are first-class:
|
||
when ANSI color0==color8 the tool derives an overlay step; light themes
|
||
do not use dark ANSI black as surface — hand-tune after import; do not
|
||
bulk-reimport shipped JSON without a hierarchy pass).
|
||
- **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
|
||
|
||
| Doc | Role |
|
||
|-----|------|
|
||
| **[agent/BACKLOG.md](agent/BACKLOG.md)** | What to do next (agent queue) |
|
||
| **[docs/VISION.md](docs/VISION.md)** | Product themes toward **v1.0** |
|
||
| **[docs/ROADMAP.md](docs/ROADMAP.md)** | Design history + shipped log |
|
||
| **[docs/README.md](docs/README.md)** · **[agent/README.md](agent/README.md)** | Maps |
|
||
| **[agent/LOOP.md](agent/LOOP.md)** | Autonomous iteration protocol |
|
||
|
||
Kept out of the README body so this stays a focused entry point.
|