refactor(#107): theme-state.json → state.json, theme-sync → state-sync
Some checks failed
Check / eval (push) Has been cancelled
Some checks failed
Check / eval (push) Has been cancelled
The machine flake's git-tracked settings file is system state, not "theme" only — rename it to state.json. CLI becomes nomarchy-state-sync with a nomarchy-theme-sync symlink for scripts and muscle memory. Eval (mkFlake, doctor, lifecycle) still accepts theme-state.json; the next write migrates to state.json and removes the legacy file. Documented in MIGRATION.md; drop the CLI alias after release notes.
This commit is contained in:
42
README.md
42
README.md
@@ -7,11 +7,11 @@ partial.
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────────────────────┐
|
||||||
│ theme-state.json (single source of truth) │
|
│ state.json (single source of truth) │
|
||||||
│ lives INSIDE your flake checkout, git-tracked │
|
│ lives INSIDE your flake checkout, git-tracked │
|
||||||
└───────────────────────────────────┬──────────────────────────────────────┘
|
└───────────────────────────────────┬──────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
nomarchy-theme-sync apply gruvbox
|
nomarchy-state-sync apply gruvbox
|
||||||
1. merges the preset into the JSON (atomic write)
|
1. merges the preset into the JSON (atomic write)
|
||||||
2. runs `home-manager switch` (no sudo, no system rebuild)
|
2. runs `home-manager switch` (no sudo, no system rebuild)
|
||||||
│
|
│
|
||||||
@@ -35,7 +35,7 @@ Flat on purpose. Two module trees, one options file each, no hidden layers.
|
|||||||
.
|
.
|
||||||
├── flake.nix # inputs + the downstream API (exports below)
|
├── flake.nix # inputs + the downstream API (exports below)
|
||||||
├── lib.nix # nomarchy.lib.mkFlake — one-call downstream wrapper
|
├── lib.nix # nomarchy.lib.mkFlake — one-call downstream wrapper
|
||||||
├── theme-state.json # ★ THE single source of truth (git-tracked!)
|
├── state.json # ★ THE single source of truth (git-tracked!)
|
||||||
├── themes/ # 24 presets: <slug>.json + optional <slug>/ assets
|
├── themes/ # 24 presets: <slug>.json + optional <slug>/ assets
|
||||||
│ ├── nord.json # palette (required, works alone)
|
│ ├── nord.json # palette (required, works alone)
|
||||||
│ └── nord/ # assets (optional, fixed filenames)
|
│ └── nord/ # assets (optional, fixed filenames)
|
||||||
@@ -72,10 +72,10 @@ Flat on purpose. Two module trees, one options file each, no hidden layers.
|
|||||||
│ ├── default/ # reference machine (thin: boot, user, hostname)
|
│ ├── default/ # reference machine (thin: boot, user, hostname)
|
||||||
│ └── live.nix # bootable live ISO (try the distro, no install)
|
│ └── live.nix # bootable live ISO (try the distro, no install)
|
||||||
├── pkgs/
|
├── pkgs/
|
||||||
│ ├── nomarchy-theme-sync/ # state writer + rebuild dispatcher (Python)
|
│ ├── nomarchy-state-sync/ # state writer + rebuild dispatcher (Python)
|
||||||
│ ├── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake)
|
│ ├── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake)
|
||||||
│ ├── nomarchy-doctor/ # read-only health sheet, one command per failure
|
│ ├── nomarchy-doctor/ # read-only health sheet, one command per failure
|
||||||
│ ├── nomarchy-control-center/ # TUI settings front-end over nomarchy-theme-sync (gum)
|
│ ├── nomarchy-control-center/ # TUI settings front-end over nomarchy-state-sync (gum)
|
||||||
│ └── nomarchy-battery-notify/ # low-battery toast watcher backing battery-notify.nix
|
│ └── nomarchy-battery-notify/ # low-battery toast watcher backing battery-notify.nix
|
||||||
├── templates/downstream/ # machine flake SoT (`flake init` + installer copy/patch)
|
├── templates/downstream/ # machine flake SoT (`flake init` + installer copy/patch)
|
||||||
├── docs/ # human docs — map: docs/README.md
|
├── docs/ # human docs — map: docs/README.md
|
||||||
@@ -161,7 +161,7 @@ Nomarchy in place, reusing your existing `hardware-configuration.nix`:
|
|||||||
**[docs/MIGRATION.md](docs/MIGRATION.md)**.
|
**[docs/MIGRATION.md](docs/MIGRATION.md)**.
|
||||||
|
|
||||||
You own two files day-to-day: `system.nix` and `home.nix` (plus
|
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 —
|
`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:
|
later by the installer — and never hand-edited; it's a single call:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
@@ -229,7 +229,7 @@ Legacy aliases still work: `sys-update` → pull+rebuild, `sys-rebuild` →
|
|||||||
`nomarchy-rebuild`, `home-update` → `nomarchy-home`.
|
`nomarchy-rebuild`, `home-update` → `nomarchy-home`.
|
||||||
|
|
||||||
Override anything via the `nomarchy.*` surface or plain NixOS/HM options:
|
Override anything via the `nomarchy.*` surface or plain NixOS/HM options:
|
||||||
appearance (gaps/colors/fonts) changes through `nomarchy-theme-sync`,
|
appearance (gaps/colors/fonts) changes through `nomarchy-state-sync`,
|
||||||
behaviour (input/misc/monitor/chrome) is `mkDefault` so a plain `home.nix`
|
behaviour (input/misc/monitor/chrome) is `mkDefault` so a plain `home.nix`
|
||||||
assignment wins, and bind/exec-once lists concatenate. Full guide with
|
assignment wins, and bind/exec-once lists concatenate. Full guide with
|
||||||
examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
||||||
@@ -243,7 +243,7 @@ two tables below are split along exactly that line.
|
|||||||
|
|
||||||
| Option | Default | Purpose |
|
| Option | Default | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `nomarchy.stateFile` | — (required) | Path to your theme-state.json |
|
| `nomarchy.stateFile` | — (required) | Path to your state.json |
|
||||||
| `nomarchy.terminal` | `"kitty"` | Terminal for keybinds and `$TERMINAL` (Kitty is the only shipped/themed emulator) |
|
| `nomarchy.terminal` | `"kitty"` | Terminal for keybinds and `$TERMINAL` (Kitty is the only shipped/themed emulator) |
|
||||||
| `nomarchy.kitty.enable` | `true` | Nomarchy's Kitty config (palette/font/opacity from theme-state) |
|
| `nomarchy.kitty.enable` | `true` | Nomarchy's Kitty config (palette/font/opacity from theme-state) |
|
||||||
| `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.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) |
|
||||||
@@ -275,7 +275,7 @@ two tables below are split along exactly that line.
|
|||||||
| `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.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.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.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 |
|
| `nomarchy.package` | overlay's `nomarchy-state-sync` | The theme/state tool package, overridable if you fork it |
|
||||||
|
|
||||||
**Always-on, no toggle by design:** `services.cliphist`, `services.udiskie`
|
**Always-on, no toggle by design:** `services.cliphist`, `services.udiskie`
|
||||||
(automount + safe-removal toasts) and `services.easyeffects` (mic noise
|
(automount + safe-removal toasts) and `services.easyeffects` (mic noise
|
||||||
@@ -290,7 +290,7 @@ option, e.g. `services.easyeffects.enable = lib.mkForce false;` — see
|
|||||||
| Option | Default | Purpose |
|
| Option | Default | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
|
| `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.stateFile` | `null` | 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.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
|
||||||
| `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet |
|
| `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.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 |
|
||||||
@@ -367,13 +367,13 @@ The trap with "read a mutable file from Nix" is pure evaluation: flakes
|
|||||||
cannot read arbitrary `$HOME` paths without `--impure` (the old prototype
|
cannot read arbitrary `$HOME` paths without `--impure` (the old prototype
|
||||||
required it — never again). Nomarchy's convention: **the state file lives
|
required it — never again). Nomarchy's convention: **the state file lives
|
||||||
inside the consuming flake** and is wired via
|
inside the consuming flake** and is wired via
|
||||||
`nomarchy.stateFile = ./theme-state.json;`. Reading it is pure — it's flake
|
`nomarchy.stateFile = ./state.json;`. Reading it is pure — it's flake
|
||||||
source. It must be git-tracked (`nomarchy-theme-sync` runs
|
source. It must be git-tracked (`nomarchy-state-sync` runs
|
||||||
`git add --intent-to-add` after every write as a safety net).
|
`git add --intent-to-add` after every write as a safety net).
|
||||||
|
|
||||||
### One change = one generation
|
### One change = one generation
|
||||||
|
|
||||||
`nomarchy-theme-sync apply <theme>` merges the preset into the JSON and runs
|
`nomarchy-state-sync apply <theme>` merges the preset into the JSON and runs
|
||||||
`home-manager switch` (override the command with `$NOMARCHY_REBUILD`, or pass
|
`home-manager switch` (override the command with `$NOMARCHY_REBUILD`, or pass
|
||||||
`--no-switch` to only write). Everything is baked: Hyprland, Waybar, Kitty,
|
`--no-switch` to only write). Everything is baked: Hyprland, Waybar, Kitty,
|
||||||
btop, and — via Stylix, mapped onto base16 roles — GTK, Qt, cursors and
|
btop, and — via Stylix, mapped onto base16 roles — GTK, Qt, cursors and
|
||||||
@@ -404,7 +404,7 @@ gates the unit, the first enable rebuilds) and `settings.nightlight.on` (the
|
|||||||
instant on/off). Expect more `nomarchy.*` toggles to migrate to this pattern.
|
instant on/off). Expect more `nomarchy.*` toggles to migrate to this pattern.
|
||||||
|
|
||||||
**Auto-commit (opt-in):** System › Auto-commit makes every `apply`/`set`
|
**Auto-commit (opt-in):** System › Auto-commit makes every `apply`/`set`
|
||||||
mutation also `git commit` theme-state.json in your flake — *only* that
|
mutation also `git commit` state.json in your flake — *only* that
|
||||||
file, so unrelated dirty work is never swept up — turning your settings
|
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
|
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
|
Nix consumes the flag), the off-write is itself committed so history stays
|
||||||
@@ -432,12 +432,12 @@ asset probe).
|
|||||||
## 5. Day-to-day
|
## 5. Day-to-day
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nomarchy-theme-sync list # 24 presets (nord, gruvbox, rose-pine, …)
|
nomarchy-state-sync list # 24 presets (nord, gruvbox, rose-pine, …)
|
||||||
nomarchy-theme-sync apply kanagawa # whole desktop, one generation (~a switch)
|
nomarchy-state-sync apply kanagawa # whole desktop, one generation (~a switch)
|
||||||
nomarchy-theme-sync set ui.gapsOut 16 # tweak one knob (also a switch)
|
nomarchy-state-sync set ui.gapsOut 16 # tweak one knob (also a switch)
|
||||||
nomarchy-theme-sync bg next # cycle wallpapers — instant, no rebuild
|
nomarchy-state-sync bg next # cycle wallpapers — instant, no rebuild
|
||||||
nomarchy-theme-sync bg auto # back to the theme's default wallpaper
|
nomarchy-state-sync bg auto # back to the theme's default wallpaper
|
||||||
nomarchy-theme-sync get colors.accent
|
nomarchy-state-sync get colors.accent
|
||||||
nomarchy-pull # flake input update (no rebuild)
|
nomarchy-pull # flake input update (no rebuild)
|
||||||
nomarchy-rebuild # rebuild the system, current lock
|
nomarchy-rebuild # rebuild the system, current lock
|
||||||
nomarchy-home # rebuild just the desktop layer
|
nomarchy-home # rebuild just the desktop layer
|
||||||
@@ -484,7 +484,7 @@ reload # exec zsh (reload the shell)
|
|||||||
|
|
||||||
- **New theme:** drop a JSON into `themes/` (schema = any existing preset),
|
- **New theme:** drop a JSON into `themes/` (schema = any existing preset),
|
||||||
plus an optional `themes/<slug>/` assets directory.
|
plus an optional `themes/<slug>/` assets directory.
|
||||||
- **New themed value:** add the key to `theme-state.json` and consume it in
|
- **New themed value:** add the key to `state.json` and consume it in
|
||||||
the Nix modules. One place — there is no second renderer to keep in sync.
|
the Nix modules. One place — there is no second renderer to keep in sync.
|
||||||
- **Importing more old-distro palettes:**
|
- **Importing more old-distro palettes:**
|
||||||
`tools/import-palettes.py <palettes-dir> themes/` (roles are first-class:
|
`tools/import-palettes.py <palettes-dir> themes/` (roles are first-class:
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ always — the same on-AC question `modules/home/idle.nix`'s suspend listener
|
|||||||
already answers with `${onAc} ||`.
|
already answers with `${onAc} ||`.
|
||||||
|
|
||||||
In-flake state like the rest (`settings.*`), menu-writable, and read via
|
In-flake state like the rest (`settings.*`), menu-writable, and read via
|
||||||
`theme-state-read.nix` on the NixOS side — NOT `config.nomarchy.settings`,
|
`state-read.nix` on the NixOS side — NOT `config.nomarchy.settings`,
|
||||||
which does not exist there (ROADMAP § *NixOS-side state bridges (#116)*; copy
|
which does not exist there (ROADMAP § *NixOS-side state bridges (#116)*; copy
|
||||||
the `bluetooth.enable` shape in `modules/nixos/default.nix` and add a case to
|
the `bluetooth.enable` shape in `modules/nixos/default.nix` and add a case to
|
||||||
`checks.state-bridges`). Pass = the toggle survives a rebuild,
|
`checks.state-bridges`). Pass = the toggle survives a rebuild,
|
||||||
@@ -234,22 +234,6 @@ a suspended machine hibernates after the configured delay and resumes, and a
|
|||||||
machine without hibernate support hides the row instead of offering a
|
machine without hibernate support hides the row instead of offering a
|
||||||
suspend that never wakes.
|
suspend that never wakes.
|
||||||
|
|
||||||
### 107. Rename `theme.json` to reflect that it is the system state
|
|
||||||
|
|
||||||
The state file long ago stopped being about themes: it carries night-light,
|
|
||||||
keyboard memory, display resolution and profiles, auto-commit, services and
|
|
||||||
more, so `theme.json` now misnames its own contents and misleads anyone
|
|
||||||
reading the flake. Rename it to `state.json` (Bernardo's call, 2026-07-14 —
|
|
||||||
settled, do not revisit), and carry the `nomarchy-theme-sync` tool name along
|
|
||||||
with it.
|
|
||||||
|
|
||||||
Standalone task — do not fold it into a feature. Ships with a compatibility
|
|
||||||
shim that keeps reading an existing `theme.json` so downstream checkouts do
|
|
||||||
not break on a pull, plus a migration note. Pass = a fresh install and an
|
|
||||||
existing downstream checkout both work, every in-repo reference (modules,
|
|
||||||
tools, template, docs) uses the new name, and the shim is documented with the
|
|
||||||
release it can be dropped in.
|
|
||||||
|
|
||||||
### 104. Runtime Airplane mode
|
### 104. Runtime Airplane mode
|
||||||
|
|
||||||
Add Airplane mode under System connectivity controls. It must disable Wi-Fi
|
Add Airplane mode under System connectivity controls. It must disable Wi-Fi
|
||||||
@@ -280,7 +264,7 @@ read, so *these two toggles have never actually worked for any user*. That
|
|||||||
cuts both ways: "preserve existing behavior" is not a reason to keep them
|
cuts both ways: "preserve existing behavior" is not a reason to keep them
|
||||||
(there is no behavior to preserve), but the state keys and their bridges are
|
(there is no behavior to preserve), but the state keys and their bridges are
|
||||||
real now and outlive the TUI — dropping the rows must not drop the keys, or a
|
real now and outlive the TUI — dropping the rows must not drop the keys, or a
|
||||||
machine whose theme-state.json already says `bluetooth.enable = false` will
|
machine whose state.json already says `bluetooth.enable = false` will
|
||||||
silently flip back on at the next rebuild. `checks.state-bridges` covers those
|
silently flip back on at the next rebuild. `checks.state-bridges` covers those
|
||||||
two keys and will fail loudly if the bridges go with the tool; keep it green,
|
two keys and will fail loudly if the bridges go with the tool; keep it green,
|
||||||
and if a key is deliberately retired, remove its case in the same commit.
|
and if a key is deliberately retired, remove its case in the same commit.
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ those, it probably shouldn't exist.
|
|||||||
|
|
||||||
## Feature design
|
## Feature design
|
||||||
- **In-flake state:** any user-settable config gets a menu writer that
|
- **In-flake state:** any user-settable config gets a menu writer that
|
||||||
lands it in `theme-state.json` (`settings.*`), git-tracked. No
|
lands it in `state.json` (`settings.*`), git-tracked. No
|
||||||
`~/.local/state`, no side files. Instant-effect where possible
|
`~/.local/state`, no side files. Instant-effect where possible
|
||||||
(`--no-switch` + flip the service; the service reads the *live* working
|
(`--no-switch` + flip the service; the service reads the *live* working
|
||||||
tree at start — the night-light `ExecCondition` pattern). Rebuild-baked
|
tree at start — the night-light `ExecCondition` pattern). Rebuild-baked
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ confidence, default identity, release bar) live in
|
|||||||
on-hardware QA.
|
on-hardware QA.
|
||||||
2. **Reproducible, with zero hidden state.** The downstream flake checkout
|
2. **Reproducible, with zero hidden state.** The downstream flake checkout
|
||||||
*is* the machine. All user-settable config is menu-writable into the
|
*is* the machine. All user-settable config is menu-writable into the
|
||||||
git-tracked state file (`theme-state.json` `settings.*`) — never
|
git-tracked state file (`state.json` `settings.*`) — never
|
||||||
`~/.local/state`, never `~/.config` side files. Re-cloning your flake
|
`~/.local/state`, never `~/.config` side files. Re-cloning your flake
|
||||||
reproduces the machine, settings and all.
|
reproduces the machine, settings and all.
|
||||||
3. **Effortless to configure.** The user never has to learn Nix. Every
|
3. **Effortless to configure.** The user never has to learn Nix. Every
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ the **T14s** (webcam case).
|
|||||||
open Ghostty, so the hand `btop.theme` assets were only guest-file
|
open Ghostty, so the hand `btop.theme` assets were only guest-file
|
||||||
asserted (main_bg / inactive_fg keys) + desktop/bar rendered for
|
asserted (main_bg / inactive_fg keys) + desktop/bar rendered for
|
||||||
rose-pine, everforest, summer-night, vantablack, catppuccin. On a
|
rose-pine, everforest, summer-night, vantablack, catppuccin. On a
|
||||||
real session: `nomarchy-theme-sync apply <slug>` then `btop` and
|
real session: `nomarchy-state-sync apply <slug>` then `btop` and
|
||||||
confirm backgrounds/text match the theme (esp. rose-pine Dawn light
|
confirm backgrounds/text match the theme (esp. rose-pine Dawn light
|
||||||
bg `#faf4ed`, vantablack near-black `#0d0d0d`, catppuccin Mocha
|
bg `#faf4ed`, vantablack near-black `#0d0d0d`, catppuccin Mocha
|
||||||
`#1E1E2E`). One pass cycling those five is enough.
|
`#1E1E2E`). One pass cycling those five is enough.
|
||||||
|
|||||||
@@ -19,6 +19,17 @@ Template:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-15 — #107 state.json + nomarchy-state-sync
|
||||||
|
- **Task:** BACKLOG #107 — rename misnamed system state file and CLI.
|
||||||
|
- **Did:** `theme-state.json` → `state.json` (template, root, docs, modules);
|
||||||
|
package/CLI `nomarchy-theme-sync` → `nomarchy-state-sync` with symlink
|
||||||
|
alias; `modules/state-read.nix`; mkFlake/doctor/lifecycle read legacy
|
||||||
|
name; writes always land on `state.json` and remove the old file.
|
||||||
|
- **Verified:** py_compile; legacy-file unit migrate (write removes
|
||||||
|
theme-state.json); V0 flake check pending in same push.
|
||||||
|
- **Pending:** drop `nomarchy-theme-sync` alias after release notes.
|
||||||
|
- **Next suggestion:** #104 airplane, or #98 with a screenshot.
|
||||||
|
|
||||||
## 2026-07-15 — Close #94 installed chromium + kill Hyprland update-news
|
## 2026-07-15 — Close #94 installed chromium + kill Hyprland update-news
|
||||||
- **Task:** Residual #94 (chromium on installed path) + first-boot polish
|
- **Task:** Residual #94 (chromium on installed path) + first-boot polish
|
||||||
seen while probing.
|
seen while probing.
|
||||||
@@ -373,7 +384,7 @@ Template:
|
|||||||
(`modules/home/options.nix:412`), so `or <fallback>` had been swallowing a
|
(`modules/home/options.nix:412`), so `or <fallback>` had been swallowing a
|
||||||
missing-attribute error on the NixOS side forever. Options now declare a
|
missing-attribute error on the NixOS side forever. Options now declare a
|
||||||
*static* default and the implementing module reads the state via
|
*static* default and the implementing module reads the state via
|
||||||
`theme-state-read.nix` + `lib.mkIf (state != null) (lib.mkDefault state)` —
|
`state-read.nix` + `lib.mkIf (state != null) (lib.mkDefault state)` —
|
||||||
done for `bluetooth.enable` (default.nix) and `services.printing.enable`
|
done for `bluetooth.enable` (default.nix) and `services.printing.enable`
|
||||||
(services.nix). `batteryChargeLimit`'s read is simply deleted: power.nix's
|
(services.nix). `batteryChargeLimit`'s read is simply deleted: power.nix's
|
||||||
oneshot reads the key with `jq` at *runtime*, which is why that menu worked.
|
oneshot reads the key with `jq` at *runtime*, which is why that menu worked.
|
||||||
@@ -421,7 +432,7 @@ Template:
|
|||||||
Making both state-owned surfaced two real bugs: `greeter.autoLogin`
|
Making both state-owned surfaced two real bugs: `greeter.autoLogin`
|
||||||
defaulted from `config.nomarchy.settings…`, an attribute that exists ONLY
|
defaulted from `config.nomarchy.settings…`, an attribute that exists ONLY
|
||||||
on the HM side, and `or null` swallowed it — the state path never worked on
|
on the HM side, and `or null` swallowed it — the state path never worked on
|
||||||
any machine ever built; and `theme-sync get` printed Python's `None` for a
|
any machine ever built; and `state-sync get` printed Python's `None` for a
|
||||||
JSON null, so any `case … null)` would miss.
|
JSON null, so any `case … null)` would miss.
|
||||||
- **Verified:** **V1** — flake check, installer-safety, option-docs pass.
|
- **Verified:** **V1** — flake check, installer-safety, option-docs pass.
|
||||||
Proved by eval/build: a state carrying autoLogin yields greetd
|
Proved by eval/build: a state carrying autoLogin yields greetd
|
||||||
@@ -580,14 +591,14 @@ Template:
|
|||||||
- **Next suggestion:** wait on round 7 before touching the dock path again.
|
- **Next suggestion:** wait on round 7 before touching the dock path again.
|
||||||
|
|
||||||
## 2026-07-13 — #99 live-seed git-init; theme-switch failure filed as #113 (this commit)
|
## 2026-07-13 — #99 live-seed git-init; theme-switch failure filed as #113 (this commit)
|
||||||
- **Task:** BACKLOG #99 — false "theme-state.json NOT git-tracked" doctor/
|
- **Task:** BACKLOG #99 — false "state.json NOT git-tracked" doctor/
|
||||||
Waybar warning in the live session; resolve the held question from Fable.
|
Waybar warning in the live session; resolve the held question from Fable.
|
||||||
- **Did:** hosts/live.nix `nomarchy-seed-flake` now `git init`+`add`+`commit`s
|
- **Did:** hosts/live.nix `nomarchy-seed-flake` now `git init`+`add`+`commit`s
|
||||||
the seeded `~/.nomarchy` (like nomarchy-install.sh:654), so the live
|
the seeded `~/.nomarchy` (like nomarchy-install.sh:654), so the live
|
||||||
checkout is a committed repo. Also committed #94's live-posture comment in
|
checkout is a committed repo. Also committed #94's live-posture comment in
|
||||||
flake.nix (separate commit).
|
flake.nix (separate commit).
|
||||||
- **Verified:** V2 — focused serial-log VM run (`/tmp/nomarchy-v2-theme-99/`):
|
- **Verified:** V2 — focused serial-log VM run (`/tmp/nomarchy-v2-theme-99/`):
|
||||||
doctor prints `✔ theme-state.json is git-tracked` + `✔ flake checkout is
|
doctor prints `✔ state.json is git-tracked` + `✔ flake checkout is
|
||||||
clean`, `GIT_REPO_OK`/`TRACKED_OK` markers. Held question RESOLVED: the
|
clean`, `GIT_REPO_OK`/`TRACKED_OK` markers. Held question RESOLVED: the
|
||||||
same run's offline gruvbox switch failed with a 1176-drv source rebuild,
|
same run's offline gruvbox switch failed with a 1176-drv source rebuild,
|
||||||
but a local drv-equivalence test proved the failing
|
but a local drv-equivalence test proved the failing
|
||||||
@@ -885,14 +896,14 @@ Template:
|
|||||||
## 2026-07-12 — auto-commit sweep before pull/rebuild/home (interactive)
|
## 2026-07-12 — auto-commit sweep before pull/rebuild/home (interactive)
|
||||||
- **Task:** Bernardo's request (no BACKLOG item): with autoCommit on,
|
- **Task:** Bernardo's request (no BACKLOG item): with autoCommit on,
|
||||||
hand edits to `~/.nomarchy` (his had 4) stayed forever-dirty —
|
hand edits to `~/.nomarchy` (his had 4) stayed forever-dirty —
|
||||||
theme-sync's commit is pathspec-limited to theme-state.json by design.
|
state-sync's commit is pathspec-limited to state.json by design.
|
||||||
- **Did:** New internal `nomarchy-autocommit` in nomarchy-lifecycle:
|
- **Did:** New internal `nomarchy-autocommit` in nomarchy-lifecycle:
|
||||||
live-reads `settings.autoCommit`, commits *everything* dirty with
|
live-reads `settings.autoCommit`, commits *everything* dirty with
|
||||||
message `nomarchy: auto-commit before <label>` + file list in the
|
message `nomarchy: auto-commit before <label>` + file list in the
|
||||||
body; called (`|| true`, never blocks) at the top of nomarchy-pull /
|
body; called (`|| true`, never blocks) at the top of nomarchy-pull /
|
||||||
-rebuild / -home. Exposed as `passthru.autocommit`; package exported.
|
-rebuild / -home. Exposed as `passthru.autocommit`; package exported.
|
||||||
Docs/comments swept: README, RECOVERY, ROADMAP ✓ entry, rofi toggle
|
Docs/comments swept: README, RECOVERY, ROADMAP ✓ entry, rofi toggle
|
||||||
text, control-center prompt, theme-sync docstring.
|
text, control-center prompt, state-sync docstring.
|
||||||
- **Verified:** V1+ — `nix flake check --no-build` green; new permanent
|
- **Verified:** V1+ — `nix flake check --no-build` green; new permanent
|
||||||
`checks.lifecycle-autocommit` builds (real binary, sandbox repos:
|
`checks.lifecycle-autocommit` builds (real binary, sandbox repos:
|
||||||
sweep/clean/off/non-repo); end-to-end on a *copy* of the real
|
sweep/clean/off/non-repo); end-to-end on a *copy* of the real
|
||||||
@@ -1301,7 +1312,7 @@ Template:
|
|||||||
- **Task:** Bernardo challenged the "V3 real-session" framing — a simulated
|
- **Task:** Bernardo challenged the "V3 real-session" framing — a simulated
|
||||||
sunset IS VM-testable. He's right; I'd conflated "needs a real desktop"
|
sunset IS VM-testable. He's right; I'd conflated "needs a real desktop"
|
||||||
with "needs the generic home-manager switch."
|
with "needs the generic home-manager switch."
|
||||||
- **Did:** Added `checks.auto-theme` runNixOSTest: installs nomarchy-theme-sync,
|
- **Did:** Added `checks.auto-theme` runNixOSTest: installs nomarchy-state-sync,
|
||||||
points `NOMARCHY_DEFAULT_THEMES` at presets, stubs the switch via
|
points `NOMARCHY_DEFAULT_THEMES` at presets, stubs the switch via
|
||||||
`NOMARCHY_REBUILD` (a marker), configures the pair+schedule, then **walks
|
`NOMARCHY_REBUILD` (a marker), configures the pair+schedule, then **walks
|
||||||
the VM clock** 19:55→20:05→20:06→next-day 07:30 asserting slug goes
|
the VM clock** 19:55→20:05→20:06→next-day 07:30 asserting slug goes
|
||||||
@@ -1320,7 +1331,7 @@ Template:
|
|||||||
- **Task:** BACKLOG #79 slice 3 — the menu; completes the feature.
|
- **Task:** BACKLOG #79 slice 3 — the menu; completes the feature.
|
||||||
- **Did:** `rofi.nix` — "Auto theme (on/off)" row in Look & Feel + an
|
- **Did:** `rofi.nix` — "Auto theme (on/off)" row in Look & Feel + an
|
||||||
`autotheme)` submenu: toggle, pick day/night (from
|
`autotheme)` submenu: toggle, pick day/night (from
|
||||||
`nomarchy-theme-sync list`), set sunrise/sunset (HH:MM input, validated).
|
`nomarchy-state-sync list`), set sunrise/sunset (HH:MM input, validated).
|
||||||
Enable writes the flag + defaults the summer pair `--no-switch`, then
|
Enable writes the flag + defaults the summer pair `--no-switch`, then
|
||||||
`exec auto --force` — one rebuild installs the timer AND applies the right
|
`exec auto --force` — one rebuild installs the timer AND applies the right
|
||||||
theme. Disable is instant `--no-switch` (`auto` self-gates; the lingering
|
theme. Disable is instant `--no-switch` (`auto` self-gates; the lingering
|
||||||
@@ -1342,14 +1353,14 @@ Template:
|
|||||||
- **Did:** `modules/home/autotheme.nix` (imported in home default.nix):
|
- **Did:** `modules/home/autotheme.nix` (imported in home default.nix):
|
||||||
`systemd.user.{service,timer}.nomarchy-auto-theme`, install gated on
|
`systemd.user.{service,timer}.nomarchy-auto-theme`, install gated on
|
||||||
`config.nomarchy.settings.autoTheme.enable or false` (state-driven, like
|
`config.nomarchy.settings.autoTheme.enable or false` (state-driven, like
|
||||||
updates.nix gates on its enable). Service runs `nomarchy-theme-sync auto`
|
updates.nix gates on its enable). Service runs `nomarchy-state-sync auto`
|
||||||
with a PATH for `home-manager`; timer `OnStartupSec=1min` +
|
with a PATH for `home-manager`; timer `OnStartupSec=1min` +
|
||||||
`OnCalendar=*:0/15` + `Persistent`. Chose periodic over exact
|
`OnCalendar=*:0/15` + `Persistent`. Chose periodic over exact
|
||||||
sunrise/sunset timers — robust to suspend/DST, and `auto` is idempotent so
|
sunrise/sunset timers — robust to suspend/DST, and `auto` is idempotent so
|
||||||
ticks are cheap (rebuild only at the transition).
|
ticks are cheap (rebuild only at the transition).
|
||||||
- **Verified:** **V1** — real-config eval (state temp-enabled, restored):
|
- **Verified:** **V1** — real-config eval (state temp-enabled, restored):
|
||||||
`settings.autoTheme.enable`→true, timer `OnCalendar`=`*:0/15`, service
|
`settings.autoTheme.enable`→true, timer `OnCalendar`=`*:0/15`, service
|
||||||
ExecStart=`… nomarchy-theme-sync auto`; disabled→no units (isolation eval
|
ExecStart=`… nomarchy-state-sync auto`; disabled→no units (isolation eval
|
||||||
condition=false). `systemd-analyze calendar '*:0/15'` valid. **V0** flake
|
condition=false). `systemd-analyze calendar '*:0/15'` valid. **V0** flake
|
||||||
check green. (Gotcha: a new .nix file is invisible to the flake until
|
check green. (Gotcha: a new .nix file is invisible to the flake until
|
||||||
`git add` — flake eval only sees tracked files.)
|
`git add` — flake eval only sees tracked files.)
|
||||||
@@ -1360,7 +1371,7 @@ Template:
|
|||||||
## 2026-07-10 — #79 slice 1: auto time-of-day theme CLI primitive
|
## 2026-07-10 — #79 slice 1: auto time-of-day theme CLI primitive
|
||||||
- **Task:** Split BACKLOG #79 (`VISION § D`, `[big]`) into 3 slices (CLI /
|
- **Task:** Split BACKLOG #79 (`VISION § D`, `[big]`) into 3 slices (CLI /
|
||||||
timer / menu); took slice 1.
|
timer / menu); took slice 1.
|
||||||
- **Did:** `nomarchy-theme-sync auto` — reads
|
- **Did:** `nomarchy-state-sync auto` — reads
|
||||||
`settings.autoTheme.{enable,day,night,sunrise,sunset}`, computes day/night
|
`settings.autoTheme.{enable,day,night,sunrise,sunset}`, computes day/night
|
||||||
by the local clock, and applies the matching preset **only if different**
|
by the local clock, and applies the matching preset **only if different**
|
||||||
(idempotent → the slice-2 timer won't rebuild needlessly). `--which`
|
(idempotent → the slice-2 timer won't rebuild needlessly). `--which`
|
||||||
@@ -1371,7 +1382,7 @@ Template:
|
|||||||
against scratch state: wide-day→day slug, 00:00–00:01 window→night,
|
against scratch state: wide-day→day slug, 00:00–00:01 window→night,
|
||||||
disabled→no-op, missing day/night→die, `auto --no-switch` writes state,
|
disabled→no-op, missing day/night→die, `auto --no-switch` writes state,
|
||||||
re-run→"already on" no-op, plain `apply` still writes (refactor intact).
|
re-run→"already on" no-op, plain `apply` still writes (refactor intact).
|
||||||
**V0** py_compile + flake check + `checks.theme-sync-validate` green.
|
**V0** py_compile + flake check + `checks.state-sync-validate` green.
|
||||||
- **Pending:** slices 2 (systemd sunrise/sunset timers + login apply, V2)
|
- **Pending:** slices 2 (systemd sunrise/sunset timers + login apply, V2)
|
||||||
and 3 (Look & Feel menu) remain under #79.
|
and 3 (Look & Feel menu) remain under #79.
|
||||||
- **Next suggestion:** #79 slice 2 — the timer/hook (V2 runNixOSTest).
|
- **Next suggestion:** #79 slice 2 — the timer/hook (V2 runNixOSTest).
|
||||||
|
|||||||
@@ -100,10 +100,10 @@ iteration would otherwise rediscover.
|
|||||||
- **NixOS release bump is a trap:** the discarded attempt
|
- **NixOS release bump is a trap:** the discarded attempt
|
||||||
(branch deleted 2026-06-22) hit a Hyprland OOM blocker; a redo is a
|
(branch deleted 2026-06-22) hit a Hyprland OOM blocker; a redo is a
|
||||||
deliberate `v2`, never part of routine lock bumps.
|
deliberate `v2`, never part of routine lock bumps.
|
||||||
- `theme-state.json` is git-tracked inside an 86 MB flake tree, so every
|
- `state.json` is git-tracked inside an 86 MB flake tree, so every
|
||||||
state write re-copies the source before eval — the wallpapers-artifact
|
state write re-copies the source before eval — the wallpapers-artifact
|
||||||
split (BACKLOG LATER) is the decided fix (§ Faster switches).
|
split (BACKLOG LATER) is the decided fix (§ Faster switches).
|
||||||
- **Friendly theme-state load** (`modules/theme-state-read.nix`, #66):
|
- **Friendly theme-state load** (`modules/state-read.nix`, #66):
|
||||||
`builtins.tryEval` does **not** catch `readFile`/`fromJSON` failures —
|
`builtins.tryEval` does **not** catch `readFile`/`fromJSON` failures —
|
||||||
gate with `pathExists` + empty/non-object checks before `fromJSON`.
|
gate with `pathExists` + empty/non-object checks before `fromJSON`.
|
||||||
Subtle JSON syntax errors still surface from nlohmann (line/col);
|
Subtle JSON syntax errors still surface from nlohmann (line/col);
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ ryzenadj)**, **no Secure Boot**, and the **stateVersion override**.
|
|||||||
nomarchy.hardware.amd.enable = true; # amd-pstate + radeonsi VA-API
|
nomarchy.hardware.amd.enable = true; # amd-pstate + radeonsi VA-API
|
||||||
# nomarchy.hardware.amd.rocm.enable = true; # opt-in GPU compute (multi-GB)
|
# nomarchy.hardware.amd.rocm.enable = true; # opt-in GPU compute (multi-GB)
|
||||||
|
|
||||||
# Auto-login is NOT set here: it lives in theme-state.json
|
# Auto-login is NOT set here: it lives in state.json
|
||||||
# (settings.greeter.autoLogin) so System › Auto-login can move it — a line
|
# (settings.greeter.autoLogin) so System › Auto-login can move it — a line
|
||||||
# here would outrank the state and pin it. Turn it on after the first boot
|
# here would outrank the state and pin it. Turn it on after the first boot
|
||||||
# with `nomarchy-autologin on` (what nomarchy-install seeds on encrypted
|
# with `nomarchy-autologin on` (what nomarchy-install seeds on encrypted
|
||||||
@@ -206,9 +206,9 @@ emacs setup).
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `theme-state.json`
|
### `state.json`
|
||||||
|
|
||||||
Copy the template's `theme-state.json` (or let `nomarchy-menu theme` write
|
Copy the template's `state.json` (or let `nomarchy-menu theme` write
|
||||||
it after the switch). Your old `nomarchy-state.nix` prototype (schema
|
it after the switch). Your old `nomarchy-state.nix` prototype (schema
|
||||||
`nomarchy.theme = "nord"` …) is **retired** — the current distro uses this
|
`nomarchy.theme = "nord"` …) is **retired** — the current distro uses this
|
||||||
JSON. `nord` is a shipped Nomarchy theme, so you lose nothing.
|
JSON. `nord` is a shipped Nomarchy theme, so you lose nothing.
|
||||||
@@ -247,7 +247,7 @@ radeonsi), display brightness.
|
|||||||
You should also get a one-shot **You're set** toast (menu / themes /
|
You should also get a one-shot **You're set** toast (menu / themes /
|
||||||
keys). If it never appears: `systemctl --user status nomarchy-first-boot`
|
keys). If it never appears: `systemctl --user status nomarchy-first-boot`
|
||||||
and re-try with
|
and re-try with
|
||||||
`nomarchy-theme-sync set settings.firstBootShown false --no-switch`
|
`nomarchy-state-sync set settings.firstBootShown false --no-switch`
|
||||||
then log out/in.
|
then log out/in.
|
||||||
|
|
||||||
If anything is wrong: **reboot → old generation.** Nothing is committed as
|
If anything is wrong: **reboot → old generation.** Nothing is committed as
|
||||||
@@ -261,7 +261,7 @@ default yet.
|
|||||||
power‑profile icons open the power menu. Your ryzenadj scripts are gone;
|
power‑profile icons open the power menu. Your ryzenadj scripts are gone;
|
||||||
if you miss a specific TDP behaviour, that's a follow‑up, not a blocker.
|
if you miss a specific TDP behaviour, that's a follow‑up, not a blocker.
|
||||||
- **Theme:** `nomarchy-menu theme` → pick a preset (writes
|
- **Theme:** `nomarchy-menu theme` → pick a preset (writes
|
||||||
`theme-state.json`).
|
`state.json`).
|
||||||
- **Snapshots:** `nomarchy-menu` → System → Snapshots should see your
|
- **Snapshots:** `nomarchy-menu` → System → Snapshots should see your
|
||||||
existing `@snapshots` subvolume.
|
existing `@snapshots` subvolume.
|
||||||
- **Fingerprint:** `fingerprint.enable = true` only starts **fprintd**
|
- **Fingerprint:** `fingerprint.enable = true` only starts **fprintd**
|
||||||
@@ -446,6 +446,25 @@ just reports that no swap is configured.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## State file rename (`theme-state.json` → `state.json`, #107)
|
||||||
|
|
||||||
|
The machine flake's git-tracked state file is **`state.json`** (appearance +
|
||||||
|
menu settings). Older checkouts may still have `theme-state.json`.
|
||||||
|
|
||||||
|
- **Eval:** `lib.mkFlake` and the reader accept either name (prefer
|
||||||
|
`state.json`).
|
||||||
|
- **Write:** `nomarchy-state-sync` (and the menu) always write `state.json`
|
||||||
|
and remove a leftover `theme-state.json` so you never have two sources.
|
||||||
|
- **CLI:** `nomarchy-state-sync` is the real name; `nomarchy-theme-sync`
|
||||||
|
remains a symlink for scripts and muscle memory. Drop the alias after the
|
||||||
|
next stable release notes call it out.
|
||||||
|
|
||||||
|
No action required on pull: the next theme apply or menu write migrates you.
|
||||||
|
To migrate by hand: `mv theme-state.json state.json && git add state.json`
|
||||||
|
(and `git rm theme-state.json` if it was tracked).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## TuringMachine — the decisions, at a glance
|
## TuringMachine — the decisions, at a glance
|
||||||
|
|
||||||
| Item | Choice |
|
| Item | Choice |
|
||||||
|
|||||||
@@ -81,11 +81,11 @@ Both distros reskin everything from one place. In Nomarchy:
|
|||||||
- **`SUPER+T`** (or `nomarchy-menu theme`) — pick from 24 presets with live
|
- **`SUPER+T`** (or `nomarchy-menu theme`) — pick from 24 presets with live
|
||||||
previews; it rebuilds and applies to Hyprland, Waybar, the terminal,
|
previews; it rebuilds and applies to Hyprland, Waybar, the terminal,
|
||||||
btop and GTK/Qt at once.
|
btop and GTK/Qt at once.
|
||||||
- Or from a shell: `nomarchy-theme-sync apply <slug>` (e.g. `gruvbox`,
|
- Or from a shell: `nomarchy-state-sync apply <slug>` (e.g. `gruvbox`,
|
||||||
`rose-pine`, `nord`).
|
`rose-pine`, `nord`).
|
||||||
- Tweak gaps / rounding / fonts / opacity / icon pack without leaving the
|
- Tweak gaps / rounding / fonts / opacity / icon pack without leaving the
|
||||||
theme system — see [OVERRIDES.md](OVERRIDES.md). One git-tracked JSON
|
theme system — see [OVERRIDES.md](OVERRIDES.md). One git-tracked JSON
|
||||||
(`theme-state.json`) is the single source of truth.
|
(`state.json`) is the single source of truth.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ rule of thumb:
|
|||||||
|
|
||||||
## 1. Appearance (gaps, colors, rounding, fonts, opacity) — use the CLI
|
## 1. Appearance (gaps, colors, rounding, fonts, opacity) — use the CLI
|
||||||
|
|
||||||
Everything that defines the *look* flows from `theme-state.json`, the single
|
Everything that defines the *look* flows from `state.json`, the single
|
||||||
source of truth. Change it with `nomarchy-theme-sync`, which writes the JSON
|
source of truth. Change it with `nomarchy-state-sync`, which writes the JSON
|
||||||
and rebuilds — one generation, applied to Hyprland, Waybar, Kitty, btop and
|
and rebuilds — one generation, applied to Hyprland, Waybar, Kitty, btop and
|
||||||
Stylix at once:
|
Stylix at once:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nomarchy-theme-sync set ui.gapsOut 16 # gaps, borders, rounding, opacity
|
nomarchy-state-sync set ui.gapsOut 16 # gaps, borders, rounding, opacity
|
||||||
nomarchy-theme-sync set ui.rounding 0
|
nomarchy-state-sync set ui.rounding 0
|
||||||
nomarchy-theme-sync set fonts.mono "FiraCode Nerd Font"
|
nomarchy-state-sync set fonts.mono "FiraCode Nerd Font"
|
||||||
nomarchy-theme-sync apply gruvbox # whole palette
|
nomarchy-state-sync apply gruvbox # whole palette
|
||||||
```
|
```
|
||||||
|
|
||||||
### Icon pack
|
### Icon pack
|
||||||
@@ -28,7 +28,7 @@ icon pack shipped by default (Papirus alone is ~1 GiB, so extra packs are
|
|||||||
opt-in rather than a cost every install pays). To switch to another pack:
|
opt-in rather than a cost every install pays). To switch to another pack:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nomarchy-theme-sync set icons "Tela-dark" # or "" to return to Papirus-by-mode
|
nomarchy-state-sync set icons "Tela-dark" # or "" to return to Papirus-by-mode
|
||||||
```
|
```
|
||||||
|
|
||||||
Only the pack you name is pulled into your system (the first switch downloads
|
Only the pack you name is pulled into your system (the first switch downloads
|
||||||
@@ -55,11 +55,11 @@ Turn it on from **Look & Feel › Auto theme** (`SUPER+M`): toggle it, pick the
|
|||||||
day and night themes, and set the sunrise/sunset times. The same from a shell:
|
day and night themes, and set the sunrise/sunset times. The same from a shell:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nomarchy-theme-sync set settings.autoTheme.day summer-day --no-switch
|
nomarchy-state-sync set settings.autoTheme.day summer-day --no-switch
|
||||||
nomarchy-theme-sync set settings.autoTheme.night summer-night --no-switch
|
nomarchy-state-sync set settings.autoTheme.night summer-night --no-switch
|
||||||
nomarchy-theme-sync set settings.autoTheme.sunset 20:00 --no-switch
|
nomarchy-state-sync set settings.autoTheme.sunset 20:00 --no-switch
|
||||||
nomarchy-theme-sync set settings.autoTheme.enable true --no-switch
|
nomarchy-state-sync set settings.autoTheme.enable true --no-switch
|
||||||
nomarchy-theme-sync auto --force # one rebuild: installs the timer + applies now
|
nomarchy-state-sync auto --force # one rebuild: installs the timer + applies now
|
||||||
```
|
```
|
||||||
|
|
||||||
State lives in `settings.autoTheme`:
|
State lives in `settings.autoTheme`:
|
||||||
@@ -67,14 +67,14 @@ State lives in `settings.autoTheme`:
|
|||||||
| Field | Meaning |
|
| Field | Meaning |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `enable` | on / off |
|
| `enable` | on / off |
|
||||||
| `day` / `night` | theme slugs — e.g. `summer-day` / `summer-night` (see `nomarchy-theme-sync list`) |
|
| `day` / `night` | theme slugs — e.g. `summer-day` / `summer-night` (see `nomarchy-state-sync list`) |
|
||||||
| `sunrise` / `sunset` | switch times, `"HH:MM"` (24-hour) |
|
| `sunrise` / `sunset` | switch times, `"HH:MM"` (24-hour) |
|
||||||
|
|
||||||
A timer re-checks every ~15 minutes: the day theme applies around sunrise, the
|
A timer re-checks every ~15 minutes: the day theme applies around sunrise, the
|
||||||
night theme around sunset — but it only rebuilds when the active theme
|
night theme around sunset — but it only rebuilds when the active theme
|
||||||
actually needs to change, so most checks do nothing. Enabling rebuilds once
|
actually needs to change, so most checks do nothing. Enabling rebuilds once
|
||||||
(to install the timer); disabling is instant. Preview the current decision
|
(to install the timer); disabling is instant. Preview the current decision
|
||||||
without switching with `nomarchy-theme-sync auto --which`.
|
without switching with `nomarchy-state-sync auto --which`.
|
||||||
|
|
||||||
These values are deliberately kept at normal priority in the modules, so they
|
These values are deliberately kept at normal priority in the modules, so they
|
||||||
stay owned by the theme system. If you *insist* on pinning one in `home.nix`
|
stay owned by the theme system. If you *insist* on pinning one in `home.nix`
|
||||||
@@ -197,7 +197,7 @@ value is theme-owned at normal priority — either change it via the CLI (§1) o
|
|||||||
|
|
||||||
| You want to… | Do this |
|
| You want to… | Do this |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Change gaps / colors / rounding / fonts | `nomarchy-theme-sync set …` or `apply` |
|
| Change gaps / colors / rounding / fonts | `nomarchy-state-sync set …` or `apply` |
|
||||||
| Change input / misc / monitor / animations / terminal chrome | plain assignment in `home.nix` |
|
| Change input / misc / monitor / animations / terminal chrome | plain assignment in `home.nix` |
|
||||||
| Arrange monitors declaratively | `nomarchy.monitors` (values via `nwg-displays`) |
|
| Arrange monitors declaratively | `nomarchy.monitors` (values via `nwg-displays`) |
|
||||||
| Add keybinds / autostarts | add to the `bind` / `exec-once` list (concatenates) |
|
| Add keybinds / autostarts | add to the `bind` / `exec-once` list (concatenates) |
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ home-manager generations # newest first, one per theme/HM change
|
|||||||
The same picker lives in the menu: **System › Rollback** lists the
|
The same picker lives in the menu: **System › Rollback** lists the
|
||||||
recent desktop generations — pick one and it activates.
|
recent desktop generations — pick one and it activates.
|
||||||
|
|
||||||
Or simply apply a theme you know is good: `nomarchy-theme-sync apply
|
Or simply apply a theme you know is good: `nomarchy-state-sync apply
|
||||||
boreal` (or any preset). If a switch failed halfway, the state file is written
|
boreal` (or any preset). If a switch failed halfway, the state file is written
|
||||||
*before* the rebuild — fix the cause and re-run
|
*before* the rebuild — fix the cause and re-run
|
||||||
`home-manager switch --flake ~/.nomarchy` (or `nomarchy-home`).
|
`home-manager switch --flake ~/.nomarchy` (or `nomarchy-home`).
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
## Roadmap
|
## Roadmap
|
||||||
- **Menu system** (apps launcher + theme switching + system actions), built
|
- **Menu system** (apps launcher + theme switching + system actions), built
|
||||||
on rofi 2.0 (native Wayland on 26.05) — its `.rasi` theme is baked from
|
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:
|
state.json like every other app, with rich per-element styling:
|
||||||
- ✓ shipped: `modules/home/rofi.nix` (per-element theme generated from
|
- ✓ shipped: `modules/home/rofi.nix` (per-element theme generated from
|
||||||
the palette, `themes/<slug>/rofi.rasi` whole-swap) and the
|
the palette, `themes/<slug>/rofi.rasi` whole-swap) and the
|
||||||
`nomarchy-menu` dispatcher: root picker (no args) · `power`
|
`nomarchy-menu` dispatcher: root picker (no args) · `power`
|
||||||
@@ -197,18 +197,18 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
(confirmed): `themes/` is 86 MB and `backgrounds/` is **all** of it — the
|
(confirmed): `themes/` is 86 MB and `backgrounds/` is **all** of it — the
|
||||||
palette JSONs + per-theme overrides are only ~208 KB, and the wallpapers are
|
palette JSONs + per-theme overrides are only ~208 KB, and the wallpapers are
|
||||||
**never read at Nix eval** (only the Python `swww` path uses them). But
|
**never read at Nix eval** (only the Python `swww` path uses them). But
|
||||||
`theme-state.json` is git-tracked, so every `apply` rewrites it → the flake
|
`state.json` is git-tracked, so every `apply` rewrites it → the flake
|
||||||
tree changes → Nix re-copies the whole 86 MB source before `home-manager
|
tree changes → Nix re-copies the whole 86 MB source before `home-manager
|
||||||
switch` can evaluate. You pay an 86 MB copy to change a 1 KB file.
|
switch` can evaluate. You pay an 86 MB copy to change a 1 KB file.
|
||||||
**Decided approach (deferred — don't want the extra moving part yet):**
|
**Decided approach (deferred — don't want the extra moving part yet):**
|
||||||
Option 1, a **separate pinned wallpapers artifact** — a `Nomarchy-wallpapers`
|
Option 1, a **separate pinned wallpapers artifact** — a `Nomarchy-wallpapers`
|
||||||
repo or release tarball, pulled once via a flake input / `fetchurl` (pinned
|
repo or release tarball, pulled once via a flake input / `fetchurl` (pinned
|
||||||
by hash → content-addressed, never re-copied on a state write), with
|
by hash → content-addressed, never re-copied on a state write), with
|
||||||
`nomarchy-theme-sync` reading wallpapers from that stable store path (the
|
`nomarchy-state-sync` reading wallpapers from that stable store path (the
|
||||||
`NOMARCHY_DEFAULT_THEMES` env hook already anticipates external theme
|
`NOMARCHY_DEFAULT_THEMES` env hook already anticipates external theme
|
||||||
assets). Keeps eval **pure**; the live ISO still bakes them in (fetched at
|
assets). Keeps eval **pure**; the live ISO still bakes them in (fetched at
|
||||||
build). A state write then re-copies only ~208 KB. Rejected alternative:
|
build). A state write then re-copies only ~208 KB. Rejected alternative:
|
||||||
moving `theme-state.json` out to `~/.config` + `--impure` eval (one repo, no
|
moving `state.json` out to `~/.config` + `--impure` eval (one repo, no
|
||||||
second artifact, but trades away the in-tree-pinned-state reproducibility).
|
second artifact, but trades away the in-tree-pinned-state reproducibility).
|
||||||
Follow-on if `home-manager switch` itself is still the bottleneck after the
|
Follow-on if `home-manager switch` itself is still the bottleneck after the
|
||||||
copy is gone: **pre-built theme variants** (build each theme's generation
|
copy is gone: **pre-built theme variants** (build each theme's generation
|
||||||
@@ -302,7 +302,7 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
- ✓ **Auto time-of-day theme (#79, `VISION § D`):** an automatic light/dark
|
- ✓ **Auto time-of-day theme (#79, `VISION § D`):** an automatic light/dark
|
||||||
theme switch through the **same** engine as a manual apply — no second
|
theme switch through the **same** engine as a manual apply — no second
|
||||||
pipeline. `settings.autoTheme = {enable, day, night, sunrise, sunset}`
|
pipeline. `settings.autoTheme = {enable, day, night, sunrise, sunset}`
|
||||||
drives `nomarchy-theme-sync auto`, which picks day vs night by the local
|
drives `nomarchy-state-sync auto`, which picks day vs night by the local
|
||||||
clock and applies the preset **only if it differs** (idempotent).
|
clock and applies the preset **only if it differs** (idempotent).
|
||||||
`modules/home/autotheme.nix` installs a user timer
|
`modules/home/autotheme.nix` installs a user timer
|
||||||
(`OnStartupSec` + `OnCalendar=*:0/15` + `Persistent`, gated on the state
|
(`OnStartupSec` + `OnCalendar=*:0/15` + `Persistent`, gated on the state
|
||||||
@@ -329,7 +329,7 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
`settings.{bluetooth,printing}.enable` and printed "requires rebuild", and
|
`settings.{bluetooth,printing}.enable` and printed "requires rebuild", and
|
||||||
the rebuild changed nothing, for as long as those toggles had existed.
|
the rebuild changed nothing, for as long as those toggles had existed.
|
||||||
**The pattern, now uniform:** the option declares a *static* default and
|
**The pattern, now uniform:** the option declares a *static* default and
|
||||||
the implementing module reads the state via `modules/theme-state-read.nix`
|
the implementing module reads the state via `modules/state-read.nix`
|
||||||
(fails closed on bad JSON) and `mkDefault`s it —
|
(fails closed on bad JSON) and `mkDefault`s it —
|
||||||
`lib.mkIf (stateX != null) (lib.mkDefault stateX)`, so an absent key leaves
|
`lib.mkIf (stateX != null) (lib.mkDefault stateX)`, so an absent key leaves
|
||||||
the option default as the single source of the fallback and a hand-set
|
the option default as the single source of the fallback and a hand-set
|
||||||
@@ -427,6 +427,11 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
default `$TERMINAL` / SUPER+Return, doctor + calendar + what-changed use
|
default `$TERMINAL` / SUPER+Return, doctor + calendar + what-changed use
|
||||||
`kitty --class=com.nomarchy.*`. Ghostty module and install-time GL probe
|
`kitty --class=com.nomarchy.*`. Ghostty module and install-time GL probe
|
||||||
removed. V3: Acer SUPER+Return + doctor float.
|
removed. V3: Acer SUPER+Return + doctor float.
|
||||||
|
- ✓ **State file rename (#107):** `theme-state.json` → `state.json`; CLI
|
||||||
|
`nomarchy-theme-sync` → `nomarchy-state-sync` (old binary name kept as a
|
||||||
|
symlink). Eval/read shims accept the legacy file name; the next write
|
||||||
|
migrates. Documented in MIGRATION.md. Drop the CLI alias after the next
|
||||||
|
stable release notes call it out.
|
||||||
- ✓ **Installed default browser path (#94 residual):** live half closed by
|
- ✓ **Installed default browser path (#94 residual):** live half closed by
|
||||||
#103 + `checks.live-baseline-apps`. Installed path V2 (2026-07-15): re-boot
|
#103 + `checks.live-baseline-apps`. Installed path V2 (2026-07-15): re-boot
|
||||||
of offline `test-install` disk — `chromium --version` on PATH (150.x),
|
of offline `test-install` disk — `chromium --version` on PATH (150.x),
|
||||||
@@ -1112,7 +1117,7 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
`settings.nightlight.on` (runtime on/off). **Off by default.** The *first*
|
`settings.nightlight.on` (runtime on/off). **Off by default.** The *first*
|
||||||
enable from the menu writes `installed` and rebuilds to create the unit (the
|
enable from the menu writes `installed` and rebuilds to create the unit (the
|
||||||
one accepted rebuild); **every toggle after is instant** —
|
one accepted rebuild); **every toggle after is instant** —
|
||||||
`nomarchy-theme-sync set settings.nightlight.on … --no-switch` (atomic +
|
`nomarchy-state-sync set settings.nightlight.on … --no-switch` (atomic +
|
||||||
`git add -N`, no rebuild) plus a `systemctl` start/stop. Splitting the sticky
|
`git add -N`, no rebuild) plus a `systemctl` start/stop. Splitting the sticky
|
||||||
flag from the on/off is what avoids a **decay** bug: an instant-off writes
|
flag from the on/off is what avoids a **decay** bug: an instant-off writes
|
||||||
only `on`, so an unrelated later rebuild (e.g. `sys-update`) never drops the
|
only `on`, so an unrelated later rebuild (e.g. `sys-update`) never drops the
|
||||||
@@ -1143,7 +1148,7 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
(opt-in, off by default) — geoclue + `services.automatic-timezoned` drive
|
(opt-in, off by default) — geoclue + `services.automatic-timezoned` drive
|
||||||
`/etc/localtime` from your location, so travelling to another zone updates the
|
`/etc/localtime` from your location, so travelling to another zone updates the
|
||||||
Waybar clock on its own. **Menu-driven, in-flake state** (the night-light /
|
Waybar clock on its own. **Menu-driven, in-flake state** (the night-light /
|
||||||
keyboard philosophy): the flag is `settings.autoTimezone` in theme-state.json,
|
keyboard philosophy): the flag is `settings.autoTimezone` in state.json,
|
||||||
git-tracked; the System-menu entry (`nomarchy-menu autotimezone` →
|
git-tracked; the System-menu entry (`nomarchy-menu autotimezone` →
|
||||||
`nomarchy-autotimezone`) writes it and rebuilds. **Not instant like
|
`nomarchy-autotimezone`) writes it and rebuilds. **Not instant like
|
||||||
night-light** — it's a *system* service (not a user unit you can start/stop),
|
night-light** — it's a *system* service (not a user unit you can start/stop),
|
||||||
@@ -1318,10 +1323,10 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
- ✓ **Opt-in auto-commit of state mutations (in-flake state, Phase 4):**
|
- ✓ **Opt-in auto-commit of state mutations (in-flake state, Phase 4):**
|
||||||
`settings.autoCommit` (menu: **System › Auto-commit**, self-gated on the
|
`settings.autoCommit` (menu: **System › Auto-commit**, self-gated on the
|
||||||
flake being a git repo) makes every `apply`/`set` also `git commit`
|
flake being a git repo) makes every `apply`/`set` also `git commit`
|
||||||
theme-state.json in the downstream flake. Design decisions: the flag is
|
state.json in the downstream flake. Design decisions: the flag is
|
||||||
**live-read by the tool** on each write — nothing in Nix consumes it, so
|
**live-read by the tool** on each write — nothing in Nix consumes it, so
|
||||||
the toggle is instant, no rebuild, no option-surface addition; the commit
|
the toggle is instant, no rebuild, no option-surface addition; the commit
|
||||||
is **pathspec-limited** (`git commit -- theme-state.json`) so unrelated
|
is **pathspec-limited** (`git commit -- state.json`) so unrelated
|
||||||
dirty files are never swept up; it fires when the flag is on before *or*
|
dirty files are never swept up; it fires when the flag is on before *or*
|
||||||
after the write, so the disable-toggle itself lands in history instead of
|
after the write, so the disable-toggle itself lands in history instead of
|
||||||
staying forever-dirty; a same-value `set` no-ops (diff against HEAD); a
|
staying forever-dirty; a same-value `set` no-ops (diff against HEAD); a
|
||||||
@@ -1347,7 +1352,7 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
the real binary: sweep, no-op on clean/off/non-repo).
|
the real binary: sweep, no-op on clean/off/non-repo).
|
||||||
- **"nomarchy" control center:** a single TUI/GUI front-end over the common
|
- **"nomarchy" control center:** a single TUI/GUI front-end over the common
|
||||||
toggles — theme, power profile, opt-in services, display, DND — built on
|
toggles — theme, power profile, opt-in services, display, DND — built on
|
||||||
the same `nomarchy-theme-sync` / `nomarchy.*` surface the menu already
|
the same `nomarchy-state-sync` / `nomarchy.*` surface the menu already
|
||||||
uses. Plus a first-boot welcome that lands new installs in a guided "pick
|
uses. Plus a first-boot welcome that lands new installs in a guided "pick
|
||||||
your theme / essentials" flow (ties into the branding work).
|
your theme / essentials" flow (ties into the branding work).
|
||||||
|
|
||||||
@@ -1390,11 +1395,11 @@ Design/decision records and a running log of shipped work (items marked
|
|||||||
hardware QA, 2026-07-04): exec-once has no supervisor, so any crash
|
hardware QA, 2026-07-04): exec-once has no supervisor, so any crash
|
||||||
orphaned the session until relogin — and the switch path itself was
|
orphaned the session until relogin — and the switch path itself was
|
||||||
crash-prone: `reload_style_on_change` (inotify on style.css) and
|
crash-prone: `reload_style_on_change` (inotify on style.css) and
|
||||||
theme-sync's SIGUSR2 both fired while the HM symlinks flipped, a
|
state-sync's SIGUSR2 both fired while the HM symlinks flipped, a
|
||||||
double-reload race in waybar's in-place reload. Fix: (1) the bar runs
|
double-reload race in waybar's in-place reload. Fix: (1) the bar runs
|
||||||
under a `nomarchy-waybar` supervisor (waybar.nix) — ANY exit respawns
|
under a `nomarchy-waybar` supervisor (waybar.nix) — ANY exit respawns
|
||||||
it, with a crash-loop guard (5 fast exits → critical notification,
|
it, with a crash-loop guard (5 fast exits → critical notification,
|
||||||
stop); (2) theme-sync now prefers a clean `pkill -x waybar` when it
|
stop); (2) state-sync now prefers a clean `pkill -x waybar` when it
|
||||||
sees the supervisor (a restart with fresh config+style — no reload
|
sees the supervisor (a restart with fresh config+style — no reload
|
||||||
code path at all), keeping SIGUSR2 only as the fallback for
|
code path at all), keeping SIGUSR2 only as the fallback for
|
||||||
unsupervised/custom bars; `reload_style_on_change` stays for manual
|
unsupervised/custom bars; `reload_style_on_change` stays for manual
|
||||||
|
|||||||
@@ -72,10 +72,10 @@ Work through these in order; each one exercises a different layer.
|
|||||||
| 2 | Waybar shows at top, themed (blue accent on dark) | HM waybar module, palette baking |
|
| 2 | Waybar shows at top, themed (blue accent on dark) | HM waybar module, palette baking |
|
||||||
| 3 | `SUPER+Return` opens Kitty with the default theme colors (Boreal) | terminal default, ANSI palette |
|
| 3 | `SUPER+Return` opens Kitty with the default theme colors (Boreal) | terminal default, ANSI palette |
|
||||||
| 4 | `btop` in the terminal is themed | per-theme asset baking |
|
| 4 | `btop` in the terminal is themed | per-theme asset baking |
|
||||||
| 5 | `nomarchy-theme-sync list` prints 24 presets | package, baked themes dir |
|
| 5 | `nomarchy-state-sync list` prints 24 presets | package, baked themes dir |
|
||||||
| 6 | `nomarchy-theme-sync apply boreal` (or re-apply the **current** theme) offline → switch succeeds. Applying a *different* preset may need a network — the ISO pins the default theme's generation, not every preset (#113) | state write + offline rebuild contract |
|
| 6 | `nomarchy-state-sync apply boreal` (or re-apply the **current** theme) offline → switch succeeds. Applying a *different* preset may need a network — the ISO pins the default theme's generation, not every preset (#113) | state write + offline rebuild contract |
|
||||||
| 7 | `SUPER+SHIFT+T` cycles wallpapers instantly (try `tokyo-night` for 4, or `boreal` for its set) | the runtime wallpaper path |
|
| 7 | `SUPER+SHIFT+T` cycles wallpapers instantly (try `tokyo-night` for 4, or `boreal` for its set) | the runtime wallpaper path |
|
||||||
| 8 | `nomarchy-theme-sync apply summer-night` → after the switch the bar has its own identity (light bar, different styling) | whole-swap waybar.css assets |
|
| 8 | `nomarchy-state-sync apply summer-night` → after the switch the bar has its own identity (light bar, different styling) | whole-swap waybar.css assets |
|
||||||
| 9 | Open a GTK app — dark theme matching the palette | Stylix layer |
|
| 9 | Open a GTK app — dark theme matching the palette | Stylix layer |
|
||||||
| 10 | `home-manager generations` lists one generation per theme change; activating an older one rolls the theme back | atomicity / rollback story |
|
| 10 | `home-manager generations` lists one generation per theme change; activating an older one rolls the theme back | atomicity / rollback story |
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ Hyprland's headless backend otherwise aborts in a VM-only zero-output state.
|
|||||||
|
|
||||||
- Session/login problems: `journalctl -b -u greetd`, then `journalctl
|
- Session/login problems: `journalctl -b -u greetd`, then `journalctl
|
||||||
--user -b`.
|
--user -b`.
|
||||||
- Theme switch failures: run `nomarchy-theme-sync apply <x>` from a
|
- Theme switch failures: run `nomarchy-state-sync apply <x>` from a
|
||||||
terminal — the home-manager output streams there. The state file is
|
terminal — the home-manager output streams there. The state file is
|
||||||
written *before* the rebuild, so after fixing you can just re-run
|
written *before* the rebuild, so after fixing you can just re-run
|
||||||
`home-manager switch --flake ~/.nomarchy`.
|
`home-manager switch --flake ~/.nomarchy`.
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ Reference: [HARDWARE.md](HARDWARE.md) §4–§5, §11 (doctor).
|
|||||||
| Idea | Intent | Notes |
|
| Idea | Intent | Notes |
|
||||||
|------|--------|-------|
|
|------|--------|-------|
|
||||||
| **Silent first-week card** ✓ | One dismissible “you’re set” (menu, themes, keys, network) — not a wizard | ✓ shipped #81 — `settings.firstBootShown` + notify; live ISO keeps its own toast |
|
| **Silent first-week card** ✓ | One dismissible “you’re set” (menu, themes, keys, network) — not a wizard | ✓ shipped #81 — `settings.firstBootShown` + notify; live ISO keeps its own toast |
|
||||||
| **Boreal as default** ✓ | Identity on first boot | ✓ seed `theme-state.json` + install path use Boreal (2026-07-09) |
|
| **Boreal as default** ✓ | Identity on first boot | ✓ seed `state.json` + install path use Boreal (2026-07-09) |
|
||||||
| **Generation readability** ✓ | “What changed last rebuild” in plain language | ✓ shipped #82 — nvd toast + System › What changed? |
|
| **Generation readability** ✓ | “What changed last rebuild” in plain language | ✓ shipped #82 — nvd toast + System › What changed? |
|
||||||
| **Post-install hints** | One-shot MOTD/notify for fwupd / fprintd when relevant | Partial: MOTD + control-center tips; still no self-gated *first-session* toast when hardware is present (see BACKLOG PROPOSED) |
|
| **Post-install hints** | One-shot MOTD/notify for fwupd / fprintd when relevant | Partial: MOTD + control-center tips; still no self-gated *first-session* toast when hardware is present (see BACKLOG PROPOSED) |
|
||||||
|
|
||||||
|
|||||||
92
flake.nix
92
flake.nix
@@ -75,12 +75,12 @@
|
|||||||
# ─── Downstream API ────────────────────────────────────────────
|
# ─── Downstream API ────────────────────────────────────────────
|
||||||
# A user's machine flake imports these and layers its own
|
# A user's machine flake imports these and layers its own
|
||||||
# system.nix / home.nix on top (see templates/downstream).
|
# system.nix / home.nix on top (see templates/downstream).
|
||||||
# Their theme-state.json lives in THEIR flake and is wired up via
|
# Their state.json lives in THEIR flake and is wired up via
|
||||||
# the `nomarchy.stateFile` option — reading it stays pure.
|
# the `nomarchy.stateFile` option — reading it stays pure.
|
||||||
|
|
||||||
overlays.default = final: prev: {
|
overlays.default = final: prev: {
|
||||||
nomarchy-theme-sync = final.callPackage ./pkgs/nomarchy-theme-sync {
|
nomarchy-state-sync = final.callPackage ./pkgs/nomarchy-state-sync {
|
||||||
# Presets baked into the package, so `nomarchy-theme-sync list`
|
# Presets baked into the package, so `nomarchy-state-sync list`
|
||||||
# works on machines that don't check out this repo.
|
# works on machines that don't check out this repo.
|
||||||
themesDir = ./themes;
|
themesDir = ./themes;
|
||||||
};
|
};
|
||||||
@@ -127,7 +127,9 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
packages.${system} = {
|
packages.${system} = {
|
||||||
nomarchy-theme-sync = pkgs.nomarchy-theme-sync;
|
nomarchy-state-sync = pkgs.nomarchy-state-sync;
|
||||||
|
# #107 compat alias — same derivation; drop after release notes say so.
|
||||||
|
nomarchy-theme-sync = pkgs.nomarchy-state-sync;
|
||||||
nomarchy-install = nomarchyInstall;
|
nomarchy-install = nomarchyInstall;
|
||||||
# Overlay tools exported for `nix build .#nomarchy-doctor` etc.
|
# Overlay tools exported for `nix build .#nomarchy-doctor` etc.
|
||||||
# (maintainer/CI convenience — modules install them via the overlay).
|
# (maintainer/CI convenience — modules install them via the overlay).
|
||||||
@@ -138,7 +140,7 @@
|
|||||||
nomarchy-first-boot = pkgs.nomarchy-first-boot;
|
nomarchy-first-boot = pkgs.nomarchy-first-boot;
|
||||||
nomarchy-what-changed = pkgs.nomarchy-what-changed;
|
nomarchy-what-changed = pkgs.nomarchy-what-changed;
|
||||||
nomarchy-detect-hw = pkgs.nomarchy-detect-hw;
|
nomarchy-detect-hw = pkgs.nomarchy-detect-hw;
|
||||||
default = pkgs.nomarchy-theme-sync;
|
default = pkgs.nomarchy-state-sync;
|
||||||
};
|
};
|
||||||
|
|
||||||
# ─── Checks ────────────────────────────────────────────────────
|
# ─── Checks ────────────────────────────────────────────────────
|
||||||
@@ -155,11 +157,11 @@
|
|||||||
hardwareProfile = "framework-13-7040-amd"; # exercises the mapping
|
hardwareProfile = "framework-13-7040-amd"; # exercises the mapping
|
||||||
};
|
};
|
||||||
|
|
||||||
# theme-state.json fixtures for the state-bridge checks below,
|
# state.json fixtures for the state-bridge checks below,
|
||||||
# built off the real shipped template so they stay honest about
|
# built off the real shipped template so they stay honest about
|
||||||
# what a machine actually carries.
|
# what a machine actually carries.
|
||||||
baseState = builtins.fromJSON
|
baseState = builtins.fromJSON
|
||||||
(builtins.readFile ./templates/downstream/theme-state.json);
|
(builtins.readFile ./templates/downstream/state.json);
|
||||||
stateFileOf = name: overrides: pkgs.writeText name
|
stateFileOf = name: overrides: pkgs.writeText name
|
||||||
(builtins.toJSON (nixpkgs.lib.recursiveUpdate baseState overrides));
|
(builtins.toJSON (nixpkgs.lib.recursiveUpdate baseState overrides));
|
||||||
# The shipped template carries none of these keys — the
|
# The shipped template carries none of these keys — the
|
||||||
@@ -236,7 +238,7 @@
|
|||||||
throw ''
|
throw ''
|
||||||
State bridge broken — a settings.* key no longer reaches the config:
|
State bridge broken — a settings.* key no longer reaches the config:
|
||||||
${nixpkgs.lib.concatMapStringsSep "\n" (c: " ✗ ${c.n}") failed}
|
${nixpkgs.lib.concatMapStringsSep "\n" (c: " ✗ ${c.n}") failed}
|
||||||
Read the state via modules/theme-state-read.nix and mkDefault it
|
Read the state via modules/state-read.nix and mkDefault it
|
||||||
in the module — `config.nomarchy.settings` does NOT exist on the
|
in the module — `config.nomarchy.settings` does NOT exist on the
|
||||||
NixOS side, and `or <fallback>` swallows that silently.
|
NixOS side, and `or <fallback>` swallows that silently.
|
||||||
See ROADMAP § "NixOS-side state bridges (#116)".''
|
See ROADMAP § "NixOS-side state bridges (#116)".''
|
||||||
@@ -330,53 +332,53 @@
|
|||||||
# reports broken fixtures by field with a fix, good state
|
# reports broken fixtures by field with a fix, good state
|
||||||
# passes, and an invalid `set` is refused BEFORE the write —
|
# passes, and an invalid `set` is refused BEFORE the write —
|
||||||
# the on-disk file must be byte-identical after the attempt.
|
# the on-disk file must be byte-identical after the attempt.
|
||||||
theme-sync-validate = pkgs.runCommand "nomarchy-theme-sync-validate"
|
state-sync-validate = pkgs.runCommand "nomarchy-state-sync-validate"
|
||||||
{ nativeBuildInputs = [ pkgs.python3 pkgs.jq ]; }
|
{ nativeBuildInputs = [ pkgs.python3 pkgs.jq ]; }
|
||||||
''
|
''
|
||||||
set -eu
|
set -eu
|
||||||
tool=${./pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py}
|
tool=${./pkgs/nomarchy-state-sync/nomarchy-state-sync.py}
|
||||||
export NOMARCHY_DEFAULT_THEMES=${./themes}
|
export NOMARCHY_DEFAULT_THEMES=${./themes}
|
||||||
|
|
||||||
mkdir good && cp ${./templates/downstream/theme-state.json} good/theme-state.json
|
mkdir good && cp ${./templates/downstream/state.json} good/state.json
|
||||||
chmod +w good/theme-state.json
|
chmod +w good/state.json
|
||||||
NOMARCHY_PATH=$PWD/good python3 "$tool" validate
|
NOMARCHY_PATH=$PWD/good python3 "$tool" validate
|
||||||
|
|
||||||
mkdir syntax && printf '{ "slug": "x", }' > syntax/theme-state.json
|
mkdir syntax && printf '{ "slug": "x", }' > syntax/state.json
|
||||||
if NOMARCHY_PATH=$PWD/syntax python3 "$tool" validate 2>err; then
|
if NOMARCHY_PATH=$PWD/syntax python3 "$tool" validate 2>err; then
|
||||||
echo "FAIL: syntax fixture accepted"; exit 1; fi
|
echo "FAIL: syntax fixture accepted"; exit 1; fi
|
||||||
grep -q "syntax error at line" err
|
grep -q "syntax error at line" err
|
||||||
|
|
||||||
mkdir badhex && jq '.colors.accent = "7aa2f7"' \
|
mkdir badhex && jq '.colors.accent = "7aa2f7"' \
|
||||||
good/theme-state.json > badhex/theme-state.json
|
good/state.json > badhex/state.json
|
||||||
if NOMARCHY_PATH=$PWD/badhex python3 "$tool" validate 2>err; then
|
if NOMARCHY_PATH=$PWD/badhex python3 "$tool" validate 2>err; then
|
||||||
echo "FAIL: bad hex accepted"; exit 1; fi
|
echo "FAIL: bad hex accepted"; exit 1; fi
|
||||||
grep -q "colors.accent" err && grep -q "fix:" err
|
grep -q "colors.accent" err && grep -q "fix:" err
|
||||||
|
|
||||||
mkdir badtype && jq '.ui.gapsOut = "12"' \
|
mkdir badtype && jq '.ui.gapsOut = "12"' \
|
||||||
good/theme-state.json > badtype/theme-state.json
|
good/state.json > badtype/state.json
|
||||||
if NOMARCHY_PATH=$PWD/badtype python3 "$tool" validate 2>err; then
|
if NOMARCHY_PATH=$PWD/badtype python3 "$tool" validate 2>err; then
|
||||||
echo "FAIL: bad type accepted"; exit 1; fi
|
echo "FAIL: bad type accepted"; exit 1; fi
|
||||||
grep -q "ui.gapsOut" err
|
grep -q "ui.gapsOut" err
|
||||||
|
|
||||||
mkdir unknown && jq '.extraKey = 1' \
|
mkdir unknown && jq '.extraKey = 1' \
|
||||||
good/theme-state.json > unknown/theme-state.json
|
good/state.json > unknown/state.json
|
||||||
NOMARCHY_PATH=$PWD/unknown python3 "$tool" validate \
|
NOMARCHY_PATH=$PWD/unknown python3 "$tool" validate \
|
||||||
| grep -q "unknown top-level key"
|
| grep -q "unknown top-level key"
|
||||||
|
|
||||||
cp good/theme-state.json before.json
|
cp good/state.json before.json
|
||||||
if NOMARCHY_PATH=$PWD/good python3 "$tool" --quiet \
|
if NOMARCHY_PATH=$PWD/good python3 "$tool" --quiet \
|
||||||
set ui.gapsOut '"12"' --no-switch 2>err; then
|
set ui.gapsOut '"12"' --no-switch 2>err; then
|
||||||
echo "FAIL: invalid set was written"; exit 1; fi
|
echo "FAIL: invalid set was written"; exit 1; fi
|
||||||
grep -q "refusing to write" err
|
grep -q "refusing to write" err
|
||||||
cmp good/theme-state.json before.json
|
cmp good/state.json before.json
|
||||||
|
|
||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# The lifecycle "sweep before switch": with settings.autoCommit
|
# The lifecycle "sweep before switch": with settings.autoCommit
|
||||||
# on, nomarchy-pull/-rebuild/-home first commit every dirty file
|
# on, nomarchy-pull/-rebuild/-home first commit every dirty file
|
||||||
# in the machine flake (theme-sync's own auto_commit stays
|
# in the machine flake (state-sync's own auto_commit stays
|
||||||
# pathspec-limited to theme-state.json). Exercises the real
|
# pathspec-limited to state.json). Exercises the real
|
||||||
# helper binary against sandbox repos: flag on → one commit,
|
# helper binary against sandbox repos: flag on → one commit,
|
||||||
# clean tree, fallback identity, file list in the body; clean
|
# clean tree, fallback identity, file list in the body; clean
|
||||||
# tree / flag off / no repo → untouched, never an error.
|
# tree / flag off / no repo → untouched, never an error.
|
||||||
@@ -388,7 +390,7 @@
|
|||||||
export HOME=$PWD # no global git config → fallback identity
|
export HOME=$PWD # no global git config → fallback identity
|
||||||
|
|
||||||
mkdir flake && git -C flake init -q
|
mkdir flake && git -C flake init -q
|
||||||
printf '{ "settings": { "autoCommit": true } }' > flake/theme-state.json
|
printf '{ "settings": { "autoCommit": true } }' > flake/state.json
|
||||||
echo base > flake/system.nix
|
echo base > flake/system.nix
|
||||||
git -C flake add -A
|
git -C flake add -A
|
||||||
git -C flake -c user.name=T -c user.email=t@t commit -qm init
|
git -C flake -c user.name=T -c user.email=t@t commit -qm init
|
||||||
@@ -408,7 +410,7 @@
|
|||||||
[ "$(git -C flake rev-list --count HEAD)" = 2 ]
|
[ "$(git -C flake rev-list --count HEAD)" = 2 ]
|
||||||
|
|
||||||
# Flag off → dirty stays dirty, no commit
|
# Flag off → dirty stays dirty, no commit
|
||||||
printf '{ "settings": { "autoCommit": false } }' > flake/theme-state.json
|
printf '{ "settings": { "autoCommit": false } }' > flake/state.json
|
||||||
echo more >> flake/system.nix
|
echo more >> flake/system.nix
|
||||||
NOMARCHY_PATH=$PWD/flake $tool rebuild
|
NOMARCHY_PATH=$PWD/flake $tool rebuild
|
||||||
[ "$(git -C flake rev-list --count HEAD)" = 2 ]
|
[ "$(git -C flake rev-list --count HEAD)" = 2 ]
|
||||||
@@ -416,7 +418,7 @@
|
|||||||
|
|
||||||
# Not a git repo → silent no-op, exit 0
|
# Not a git repo → silent no-op, exit 0
|
||||||
mkdir plain
|
mkdir plain
|
||||||
printf '{ "settings": { "autoCommit": true } }' > plain/theme-state.json
|
printf '{ "settings": { "autoCommit": true } }' > plain/state.json
|
||||||
NOMARCHY_PATH=$PWD/plain $tool rebuild
|
NOMARCHY_PATH=$PWD/plain $tool rebuild
|
||||||
|
|
||||||
touch $out
|
touch $out
|
||||||
@@ -1391,7 +1393,7 @@
|
|||||||
modules = [
|
modules = [
|
||||||
self.homeModules.nomarchy
|
self.homeModules.nomarchy
|
||||||
{
|
{
|
||||||
nomarchy.stateFile = ./templates/downstream/theme-state.json;
|
nomarchy.stateFile = ./templates/downstream/state.json;
|
||||||
nomarchy.nightlight = {
|
nomarchy.nightlight = {
|
||||||
enable = true;
|
enable = true;
|
||||||
latitude = "52.52";
|
latitude = "52.52";
|
||||||
@@ -1482,14 +1484,14 @@
|
|||||||
# First-session welcome toast gate (#81): fires once when
|
# First-session welcome toast gate (#81): fires once when
|
||||||
# settings.firstBootShown is unset, writes the marker via
|
# settings.firstBootShown is unset, writes the marker via
|
||||||
# theme-sync, silent on re-run; skips hostname nomarchy-live.
|
# theme-sync, silent on re-run; skips hostname nomarchy-live.
|
||||||
# notify-send + theme-sync shimmed on PATH (same pattern as
|
# notify-send + state-sync shimmed on PATH (same pattern as
|
||||||
# battery-notify). Real toast rendering stays session/V3.
|
# battery-notify). Real toast rendering stays session/V3.
|
||||||
first-boot = pkgs.testers.runNixOSTest {
|
first-boot = pkgs.testers.runNixOSTest {
|
||||||
name = "nomarchy-first-boot";
|
name = "nomarchy-first-boot";
|
||||||
nodes.machine = { ... }: {
|
nodes.machine = { ... }: {
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
pkgs.nomarchy-first-boot
|
pkgs.nomarchy-first-boot
|
||||||
pkgs.nomarchy-theme-sync
|
pkgs.nomarchy-state-sync
|
||||||
pkgs.jq
|
pkgs.jq
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -1499,8 +1501,8 @@
|
|||||||
# Writable flake checkout with a valid theme-state (template).
|
# Writable flake checkout with a valid theme-state (template).
|
||||||
machine.succeed(
|
machine.succeed(
|
||||||
"mkdir -p /tmp/fake-nomarchy && "
|
"mkdir -p /tmp/fake-nomarchy && "
|
||||||
"cp ${./templates/downstream/theme-state.json} "
|
"cp ${./templates/downstream/state.json} "
|
||||||
"/tmp/fake-nomarchy/theme-state.json"
|
"/tmp/fake-nomarchy/state.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
machine.succeed(
|
machine.succeed(
|
||||||
@@ -1521,7 +1523,7 @@
|
|||||||
assert "SUPER+M" in out and "SUPER+T" in out, f"key pointers missing:\n{out}"
|
assert "SUPER+M" in out and "SUPER+T" in out, f"key pointers missing:\n{out}"
|
||||||
assert "Network" in out or "Wi" in out, f"network pointer missing:\n{out}"
|
assert "Network" in out or "Wi" in out, f"network pointer missing:\n{out}"
|
||||||
shown = machine.succeed(
|
shown = machine.succeed(
|
||||||
f"{env} nomarchy-theme-sync get settings.firstBootShown"
|
f"{env} nomarchy-state-sync get settings.firstBootShown"
|
||||||
).strip()
|
).strip()
|
||||||
assert shown == "true", f"marker not written: {shown!r}"
|
assert shown == "true", f"marker not written: {shown!r}"
|
||||||
|
|
||||||
@@ -1532,7 +1534,7 @@
|
|||||||
|
|
||||||
# Live ISO hostname: skip even with marker cleared.
|
# Live ISO hostname: skip even with marker cleared.
|
||||||
machine.succeed(
|
machine.succeed(
|
||||||
f"{env} nomarchy-theme-sync --quiet set settings.firstBootShown false --no-switch"
|
f"{env} nomarchy-state-sync --quiet set settings.firstBootShown false --no-switch"
|
||||||
)
|
)
|
||||||
machine.succeed("rm -f /tmp/notifications")
|
machine.succeed("rm -f /tmp/notifications")
|
||||||
machine.succeed(f"{env} hostname nomarchy-live || true")
|
machine.succeed(f"{env} hostname nomarchy-live || true")
|
||||||
@@ -1810,7 +1812,7 @@
|
|||||||
|
|
||||||
# Auto time-of-day theme (#79, VISION § D): simulate crossing
|
# Auto time-of-day theme (#79, VISION § D): simulate crossing
|
||||||
# sunset and sunrise by moving the VM clock, and assert
|
# sunset and sunrise by moving the VM clock, and assert
|
||||||
# `nomarchy-theme-sync auto` switches the active theme accordingly
|
# `nomarchy-state-sync auto` switches the active theme accordingly
|
||||||
# — day stays day (no needless rebuild), sunset flips to night,
|
# — day stays day (no needless rebuild), sunset flips to night,
|
||||||
# a later tick is idempotent, sunrise flips back. The actual
|
# a later tick is idempotent, sunrise flips back. The actual
|
||||||
# `home-manager switch` is stubbed via NOMARCHY_REBUILD (that
|
# `home-manager switch` is stubbed via NOMARCHY_REBUILD (that
|
||||||
@@ -1819,7 +1821,7 @@
|
|||||||
auto-theme = pkgs.testers.runNixOSTest {
|
auto-theme = pkgs.testers.runNixOSTest {
|
||||||
name = "nomarchy-auto-theme";
|
name = "nomarchy-auto-theme";
|
||||||
nodes.machine = { pkgs, ... }: {
|
nodes.machine = { pkgs, ... }: {
|
||||||
environment.systemPackages = [ pkgs.nomarchy-theme-sync ];
|
environment.systemPackages = [ pkgs.nomarchy-state-sync ];
|
||||||
time.timeZone = "UTC"; # naive HH:MM == the clock we set
|
time.timeZone = "UTC"; # naive HH:MM == the clock we set
|
||||||
};
|
};
|
||||||
testScript = ''
|
testScript = ''
|
||||||
@@ -1830,7 +1832,7 @@
|
|||||||
# that records each invocation instead of running home-manager.
|
# that records each invocation instead of running home-manager.
|
||||||
machine.succeed("mkdir -p /root/.nomarchy")
|
machine.succeed("mkdir -p /root/.nomarchy")
|
||||||
machine.succeed(
|
machine.succeed(
|
||||||
"echo '{\"slug\":\"summer-day\"}' > /root/.nomarchy/theme-state.json"
|
"echo '{\"slug\":\"summer-day\"}' > /root/.nomarchy/state.json"
|
||||||
)
|
)
|
||||||
machine.succeed(
|
machine.succeed(
|
||||||
"printf '#!/bin/sh\\necho switch >> /root/rebuilds\\n' > /root/rebuild"
|
"printf '#!/bin/sh\\necho switch >> /root/rebuilds\\n' > /root/rebuild"
|
||||||
@@ -1847,34 +1849,34 @@
|
|||||||
"settings.autoTheme.night summer-night",
|
"settings.autoTheme.night summer-night",
|
||||||
"settings.autoTheme.sunrise 07:00",
|
"settings.autoTheme.sunrise 07:00",
|
||||||
"settings.autoTheme.sunset 20:00"]:
|
"settings.autoTheme.sunset 20:00"]:
|
||||||
machine.succeed(env + "nomarchy-theme-sync --quiet set " + kv + " --no-switch")
|
machine.succeed(env + "nomarchy-state-sync --quiet set " + kv + " --no-switch")
|
||||||
|
|
||||||
def slug():
|
def slug():
|
||||||
return machine.succeed(env + "nomarchy-theme-sync get slug").strip()
|
return machine.succeed(env + "nomarchy-state-sync get slug").strip()
|
||||||
def rebuilds():
|
def rebuilds():
|
||||||
return int(machine.succeed("wc -l < /root/rebuilds 2>/dev/null || echo 0").strip())
|
return int(machine.succeed("wc -l < /root/rebuilds 2>/dev/null || echo 0").strip())
|
||||||
|
|
||||||
# Before sunset: already the day theme → no switch, no rebuild.
|
# Before sunset: already the day theme → no switch, no rebuild.
|
||||||
machine.succeed("date -s '2026-01-02 19:55:00'")
|
machine.succeed("date -s '2026-01-02 19:55:00'")
|
||||||
machine.succeed(env + "nomarchy-theme-sync auto")
|
machine.succeed(env + "nomarchy-state-sync auto")
|
||||||
assert slug() == "summer-day", f"pre-sunset should stay day, got {slug()}"
|
assert slug() == "summer-day", f"pre-sunset should stay day, got {slug()}"
|
||||||
assert rebuilds() == 0, f"no needless rebuild, got {rebuilds()}"
|
assert rebuilds() == 0, f"no needless rebuild, got {rebuilds()}"
|
||||||
|
|
||||||
# Cross sunset → night theme, exactly one rebuild triggered.
|
# Cross sunset → night theme, exactly one rebuild triggered.
|
||||||
machine.succeed("date -s '2026-01-02 20:05:00'")
|
machine.succeed("date -s '2026-01-02 20:05:00'")
|
||||||
machine.succeed(env + "nomarchy-theme-sync auto")
|
machine.succeed(env + "nomarchy-state-sync auto")
|
||||||
assert slug() == "summer-night", f"post-sunset should be night, got {slug()}"
|
assert slug() == "summer-night", f"post-sunset should be night, got {slug()}"
|
||||||
assert rebuilds() == 1, f"sunset should trigger one rebuild, got {rebuilds()}"
|
assert rebuilds() == 1, f"sunset should trigger one rebuild, got {rebuilds()}"
|
||||||
|
|
||||||
# Another tick still after sunset → idempotent, no extra rebuild.
|
# Another tick still after sunset → idempotent, no extra rebuild.
|
||||||
machine.succeed("date -s '2026-01-02 20:06:00'")
|
machine.succeed("date -s '2026-01-02 20:06:00'")
|
||||||
machine.succeed(env + "nomarchy-theme-sync auto")
|
machine.succeed(env + "nomarchy-state-sync auto")
|
||||||
assert slug() == "summer-night"
|
assert slug() == "summer-night"
|
||||||
assert rebuilds() == 1, f"idempotent tick must not rebuild, got {rebuilds()}"
|
assert rebuilds() == 1, f"idempotent tick must not rebuild, got {rebuilds()}"
|
||||||
|
|
||||||
# Cross sunrise the next morning → back to day, second rebuild.
|
# Cross sunrise the next morning → back to day, second rebuild.
|
||||||
machine.succeed("date -s '2026-01-03 07:30:00'")
|
machine.succeed("date -s '2026-01-03 07:30:00'")
|
||||||
machine.succeed(env + "nomarchy-theme-sync auto")
|
machine.succeed(env + "nomarchy-state-sync auto")
|
||||||
assert slug() == "summer-day", f"post-sunrise should be day, got {slug()}"
|
assert slug() == "summer-day", f"post-sunrise should be day, got {slug()}"
|
||||||
assert rebuilds() == 2, f"sunrise should trigger a rebuild, got {rebuilds()}"
|
assert rebuilds() == 2, f"sunrise should trigger a rebuild, got {rebuilds()}"
|
||||||
'';
|
'';
|
||||||
@@ -1946,12 +1948,12 @@
|
|||||||
|
|
||||||
# ─── Reference host ────────────────────────────────────────────
|
# ─── Reference host ────────────────────────────────────────────
|
||||||
# Manually wired rather than via lib.mkFlake: the repo-root
|
# Manually wired rather than via lib.mkFlake: the repo-root
|
||||||
# theme-state.json is load-bearing (live ISO + in-repo CLI dev),
|
# state.json is load-bearing (live ISO + in-repo CLI dev),
|
||||||
# which doesn't match mkFlake's src-layout convention.
|
# which doesn't match mkFlake's src-layout convention.
|
||||||
# Deliberately split rebuild paths:
|
# Deliberately split rebuild paths:
|
||||||
# system → sudo nixos-rebuild switch --flake .#nomarchy (rare)
|
# system → sudo nixos-rebuild switch --flake .#nomarchy (rare)
|
||||||
# desktop → home-manager switch --flake .#nomarchy (no sudo;
|
# desktop → home-manager switch --flake .#nomarchy (no sudo;
|
||||||
# this is what `nomarchy-theme-sync apply` runs)
|
# this is what `nomarchy-state-sync apply` runs)
|
||||||
# Theme changes never touch the system layer, so they never need
|
# Theme changes never touch the system layer, so they never need
|
||||||
# root and never rebuild the world.
|
# root and never rebuild the world.
|
||||||
nixosConfigurations.nomarchy = nixpkgs.lib.nixosSystem {
|
nixosConfigurations.nomarchy = nixpkgs.lib.nixosSystem {
|
||||||
@@ -1959,7 +1961,7 @@
|
|||||||
specialArgs = { inherit username; };
|
specialArgs = { inherit username; };
|
||||||
modules = [
|
modules = [
|
||||||
self.nixosModules.nomarchy
|
self.nixosModules.nomarchy
|
||||||
{ nomarchy.system.stateFile = ./theme-state.json; }
|
{ nomarchy.system.stateFile = ./state.json; }
|
||||||
./hosts/default/configuration.nix
|
./hosts/default/configuration.nix
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -1971,7 +1973,7 @@
|
|||||||
{
|
{
|
||||||
# The single source of truth — pure read: the file is part
|
# The single source of truth — pure read: the file is part
|
||||||
# of this flake's source.
|
# of this flake's source.
|
||||||
nomarchy.stateFile = ./theme-state.json;
|
nomarchy.stateFile = ./state.json;
|
||||||
home = {
|
home = {
|
||||||
inherit username;
|
inherit username;
|
||||||
homeDirectory = "/home/${username}";
|
homeDirectory = "/home/${username}";
|
||||||
@@ -2004,7 +2006,7 @@
|
|||||||
useUserPackages = true;
|
useUserPackages = true;
|
||||||
users.${username} = { pkgs, ... }: {
|
users.${username} = { pkgs, ... }: {
|
||||||
imports = [ self.homeModules.nomarchy ];
|
imports = [ self.homeModules.nomarchy ];
|
||||||
nomarchy.stateFile = ./theme-state.json;
|
nomarchy.stateFile = ./state.json;
|
||||||
|
|
||||||
# Live-session baseline apps (ROADMAP § live-ISO baseline
|
# Live-session baseline apps (ROADMAP § live-ISO baseline
|
||||||
# apps, #103). Until now the
|
# apps, #103). Until now the
|
||||||
@@ -2045,7 +2047,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# The standalone CLI that `nomarchy-theme-sync apply` runs for
|
# The standalone CLI that `nomarchy-state-sync apply` runs for
|
||||||
# theme switching, from the same pinned input — plus the
|
# theme switching, from the same pinned input — plus the
|
||||||
# guided installer.
|
# guided installer.
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
let
|
let
|
||||||
# ISO boot branding: the Nomarchy monogram recolored to the palette accent,
|
# ISO boot branding: the Nomarchy monogram recolored to the palette accent,
|
||||||
# centred on the theme base. Built from the vendored vector logo and the
|
# centred on the theme base. Built from the vendored vector logo and the
|
||||||
# live theme-state.json (Boreal by default). The same composed image
|
# live state.json (Boreal by default). The same composed image
|
||||||
# backs both the isolinux (BIOS) splash and the GRUB (UEFI) theme below, so
|
# backs both the isolinux (BIOS) splash and the GRUB (UEFI) theme below, so
|
||||||
# the two boot paths match.
|
# the two boot paths match.
|
||||||
state = builtins.fromJSON (builtins.readFile ../theme-state.json);
|
state = builtins.fromJSON (builtins.readFile ../state.json);
|
||||||
isoColor = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback);
|
isoColor = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback);
|
||||||
accent = isoColor "accent" "B79BE8";
|
accent = isoColor "accent" "B79BE8";
|
||||||
base = isoColor "base" "21272F";
|
base = isoColor "base" "21272F";
|
||||||
@@ -156,7 +156,7 @@ in
|
|||||||
chmod -R u+w "$home/.nomarchy"
|
chmod -R u+w "$home/.nomarchy"
|
||||||
# Committed git repo, exactly like nomarchy-install produces —
|
# Committed git repo, exactly like nomarchy-install produces —
|
||||||
# otherwise nomarchy-doctor (and its Waybar badge) false-alarms
|
# otherwise nomarchy-doctor (and its Waybar badge) false-alarms
|
||||||
# "theme-state.json is NOT git-tracked" in the live session.
|
# "state.json is NOT git-tracked" in the live session.
|
||||||
(
|
(
|
||||||
cd "$home/.nomarchy"
|
cd "$home/.nomarchy"
|
||||||
${pkgs.git}/bin/git init -q
|
${pkgs.git}/bin/git init -q
|
||||||
@@ -174,8 +174,8 @@ in
|
|||||||
Welcome to the Nomarchy live environment.
|
Welcome to the Nomarchy live environment.
|
||||||
|
|
||||||
The graphical session autologins as '${username}' (no password).
|
The graphical session autologins as '${username}' (no password).
|
||||||
Theme switching: nomarchy-theme-sync apply <name> (or SUPER+T)
|
Theme switching: nomarchy-state-sync apply <name> (or SUPER+T)
|
||||||
Wallpapers: nomarchy-theme-sync bg next (or SUPER+SHIFT+T)
|
Wallpapers: nomarchy-state-sync bg next (or SUPER+SHIFT+T)
|
||||||
Install to disk: nomarchy-install
|
Install to disk: nomarchy-install
|
||||||
The flake lives at ~/.nomarchy.
|
The flake lives at ~/.nomarchy.
|
||||||
'';
|
'';
|
||||||
|
|||||||
21
lib.nix
21
lib.nix
@@ -1,5 +1,5 @@
|
|||||||
# Downstream sugar: one call wires a whole machine flake.
|
# Downstream sugar: one call wires a whole machine flake.
|
||||||
# Users own ONLY system.nix, home.nix and theme-state.json; their flake.nix
|
# Users own ONLY system.nix, home.nix and state.json; their flake.nix
|
||||||
# is generated once (by `nix flake init -t` or the future installer) and
|
# is generated once (by `nix flake init -t` or the future installer) and
|
||||||
# never hand-edited. The raw exports (nixosModules/homeModules/overlays)
|
# never hand-edited. The raw exports (nixosModules/homeModules/overlays)
|
||||||
# in flake.nix remain the escape hatch for power users.
|
# in flake.nix remain the escape hatch for power users.
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
let
|
let
|
||||||
inherit (nixpkgs) lib;
|
inherit (nixpkgs) lib;
|
||||||
|
|
||||||
# Shared pure reader (modules/theme-state-read.nix) — re-exported so
|
# Shared pure reader (modules/state-read.nix) — re-exported so
|
||||||
# power users composing without mkFlake can call it too.
|
# power users composing without mkFlake can call it too.
|
||||||
readThemeState = import ./modules/theme-state-read.nix { inherit lib; };
|
readThemeState = import ./modules/state-read.nix { inherit lib; };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit readThemeState;
|
inherit readThemeState;
|
||||||
@@ -59,13 +59,18 @@ in
|
|||||||
config.allowUnfree = true;
|
config.allowUnfree = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Early fail-closed gate: missing/empty/non-object theme-state.json
|
# Early fail-closed gate: missing/empty/non-object state.json
|
||||||
# throws here with a template + validate pointer, before module
|
# throws here with a template + validate pointer, before module
|
||||||
# evaluation buries a raw readFile/fromJSON stack. Field-level
|
# evaluation buries a raw readFile/fromJSON stack. Field-level
|
||||||
# schema still runs in modules/home/theme.nix after defaults merge.
|
# schema still runs in modules/home/theme.nix after defaults merge.
|
||||||
# Forced via seq on the whole return set — attrNames alone must not
|
# Forced via seq on the whole return set — attrNames alone must not
|
||||||
# skip the check (Nix is lazy on unused let bindings and attr values).
|
# skip the check (Nix is lazy on unused let bindings and attr values).
|
||||||
statePath = src + "/theme-state.json";
|
# #107: prefer state.json; accept legacy theme-state.json until
|
||||||
|
# the user's next menu write migrates them.
|
||||||
|
statePath =
|
||||||
|
if builtins.pathExists (src + "/state.json") then src + "/state.json"
|
||||||
|
else if builtins.pathExists (src + "/theme-state.json") then src + "/theme-state.json"
|
||||||
|
else src + "/state.json";
|
||||||
_themeState = readThemeState statePath;
|
_themeState = readThemeState statePath;
|
||||||
in
|
in
|
||||||
builtins.seq _themeState {
|
builtins.seq _themeState {
|
||||||
@@ -78,7 +83,7 @@ in
|
|||||||
[
|
[
|
||||||
nomarchy.nixosModules.nomarchy
|
nomarchy.nixosModules.nomarchy
|
||||||
# The standalone HM CLI ships with the system so theme switching
|
# The standalone HM CLI ships with the system so theme switching
|
||||||
# (`nomarchy-theme-sync apply` → `home-manager switch`) works
|
# (`nomarchy-state-sync apply` → `home-manager switch`) works
|
||||||
# out of the box — same pinned input as the desktop modules.
|
# out of the box — same pinned input as the desktop modules.
|
||||||
{ environment.systemPackages = [ home-manager.packages.${system}.home-manager ]; }
|
{ environment.systemPackages = [ home-manager.packages.${system}.home-manager ]; }
|
||||||
# System-side theme consumers (Plymouth splash background)
|
# System-side theme consumers (Plymouth splash background)
|
||||||
@@ -94,14 +99,14 @@ in
|
|||||||
|
|
||||||
# Desktop layer — every theme change, no sudo:
|
# Desktop layer — every theme change, no sudo:
|
||||||
# home-manager switch --flake .#<username>
|
# home-manager switch --flake .#<username>
|
||||||
# (`nomarchy-theme-sync apply` runs this for you.)
|
# (`nomarchy-state-sync apply` runs this for you.)
|
||||||
homeConfigurations.${username} = home-manager.lib.homeManagerConfiguration {
|
homeConfigurations.${username} = home-manager.lib.homeManagerConfiguration {
|
||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
modules = [
|
modules = [
|
||||||
nomarchy.homeModules.nomarchy
|
nomarchy.homeModules.nomarchy
|
||||||
(src + "/home.nix")
|
(src + "/home.nix")
|
||||||
{
|
{
|
||||||
# Written by nomarchy-theme-sync; reading it is pure — the
|
# Written by nomarchy-state-sync; reading it is pure — the
|
||||||
# file is part of the downstream flake's source.
|
# file is part of the downstream flake's source.
|
||||||
nomarchy.stateFile = statePath;
|
nomarchy.stateFile = statePath;
|
||||||
home = {
|
home = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Auto time-of-day theme switch (BACKLOG #79, VISION § D). A user timer
|
# Auto time-of-day theme switch (BACKLOG #79, VISION § D). A user timer
|
||||||
# periodically runs `nomarchy-theme-sync auto`, which reads
|
# periodically runs `nomarchy-state-sync auto`, which reads
|
||||||
# settings.autoTheme = { enable, day, night, sunrise, sunset } from the
|
# settings.autoTheme = { enable, day, night, sunrise, sunset } from the
|
||||||
# state file and applies the day or night preset for the current clock —
|
# state file and applies the day or night preset for the current clock —
|
||||||
# through the SAME one engine as a manual `apply` (no second pipeline).
|
# through the SAME one engine as a manual `apply` (no second pipeline).
|
||||||
@@ -20,7 +20,7 @@ lib.mkIf (config.nomarchy.settings.autoTheme.enable or false) {
|
|||||||
# The rebuild (`home-manager switch`) and its tools resolve from the
|
# The rebuild (`home-manager switch`) and its tools resolve from the
|
||||||
# system + per-user profiles — same PATH shape nomarchy-updates uses.
|
# system + per-user profiles — same PATH shape nomarchy-updates uses.
|
||||||
Environment = "PATH=/run/current-system/sw/bin:/etc/profiles/per-user/${config.home.username}/bin";
|
Environment = "PATH=/run/current-system/sw/bin:/etc/profiles/per-user/${config.home.username}/bin";
|
||||||
ExecStart = "${pkgs.nomarchy-theme-sync}/bin/nomarchy-theme-sync auto";
|
ExecStart = "${pkgs.nomarchy-state-sync}/bin/nomarchy-state-sync auto";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./options.nix # the nomarchy.* option surface
|
./options.nix # the nomarchy.* option surface
|
||||||
./theme.nix # ingests theme-state.json, owns the live-sync hooks
|
./theme.nix # ingests state.json, owns the live-sync hooks
|
||||||
./stylix.nix # GTK/Qt/cursor/fonts from the same JSON
|
./stylix.nix # GTK/Qt/cursor/fonts from the same JSON
|
||||||
./hyprland.nix
|
./hyprland.nix
|
||||||
./waybar.nix
|
./waybar.nix
|
||||||
@@ -106,8 +106,8 @@
|
|||||||
# (system nixpkgs.config alone does not cover those entry points).
|
# (system nixpkgs.config alone does not cover those entry points).
|
||||||
NIXPKGS_ALLOW_UNFREE = "1";
|
NIXPKGS_ALLOW_UNFREE = "1";
|
||||||
|
|
||||||
# Where the Nomarchy flake (and therefore theme-state.json) lives on
|
# Where the Nomarchy flake (and therefore state.json) lives on
|
||||||
# disk. nomarchy-theme-sync writes its state here; rebuilds read from
|
# disk. nomarchy-state-sync writes its state here; rebuilds read from
|
||||||
# here. Clone/symlink your flake to this path.
|
# here. Clone/symlink your flake to this path.
|
||||||
NOMARCHY_PATH = "$HOME/.nomarchy";
|
NOMARCHY_PATH = "$HOME/.nomarchy";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
Environment = [
|
Environment = [
|
||||||
"PATH=${lib.makeBinPath [
|
"PATH=${lib.makeBinPath [
|
||||||
pkgs.libnotify
|
pkgs.libnotify
|
||||||
pkgs.nomarchy-theme-sync
|
pkgs.nomarchy-state-sync
|
||||||
pkgs.coreutils
|
pkgs.coreutils
|
||||||
]}"
|
]}"
|
||||||
"NOMARCHY_PATH=%h/.nomarchy"
|
"NOMARCHY_PATH=%h/.nomarchy"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Hyprland — fully driven by config.nomarchy.theme (theme-state.json).
|
# Hyprland — fully driven by config.nomarchy.theme (state.json).
|
||||||
# Gaps, borders and colors are baked from the JSON at eval time; theme
|
# Gaps, borders and colors are baked from the JSON at eval time; theme
|
||||||
# changes arrive via `home-manager switch`.
|
# changes arrive via `home-manager switch`.
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
@@ -8,7 +8,7 @@ let
|
|||||||
c = t.colors;
|
c = t.colors;
|
||||||
inherit (config.nomarchy.lib) rgb rgba;
|
inherit (config.nomarchy.lib) rgb rgba;
|
||||||
|
|
||||||
# nomarchy-theme-sync — the in-flake state writer the keyboard watcher uses
|
# nomarchy-state-sync — the in-flake state writer the keyboard watcher uses
|
||||||
# to remember per-device layouts (same path night-light's toggle takes).
|
# to remember per-device layouts (same path night-light's toggle takes).
|
||||||
sync = lib.getExe config.nomarchy.package;
|
sync = lib.getExe config.nomarchy.package;
|
||||||
|
|
||||||
@@ -883,7 +883,7 @@ in
|
|||||||
"awww-daemon"
|
"awww-daemon"
|
||||||
# Paint the wallpaper as soon as the session is up (waits for
|
# Paint the wallpaper as soon as the session is up (waits for
|
||||||
# the daemon internally).
|
# the daemon internally).
|
||||||
"nomarchy-theme-sync wallpaper"
|
"nomarchy-state-sync wallpaper"
|
||||||
# Polkit authentication agent — without one, EVERY pkexec/polkit
|
# Polkit authentication agent — without one, EVERY pkexec/polkit
|
||||||
# prompt in the session fails silently (btrfs-assistant-launcher
|
# prompt in the session fails silently (btrfs-assistant-launcher
|
||||||
# was the discovery case). hyprpolkitagent is Hyprland's own Qt
|
# was the discovery case). hyprpolkitagent is Hyprland's own Qt
|
||||||
@@ -903,8 +903,8 @@ in
|
|||||||
];
|
];
|
||||||
|
|
||||||
# ── Theme-driven look ──────────────────────────────────────────
|
# ── Theme-driven look ──────────────────────────────────────────
|
||||||
# These flow from theme-state.json and stay at NORMAL priority:
|
# These flow from state.json and stay at NORMAL priority:
|
||||||
# change them with `nomarchy-theme-sync set ui.<key>` (the intended
|
# change them with `nomarchy-state-sync set ui.<key>` (the intended
|
||||||
# path), or lib.mkForce in home.nix to hardcode against the theme.
|
# path), or lib.mkForce in home.nix to hardcode against the theme.
|
||||||
# The non-theme knobs around them are lib.mkDefault — override
|
# The non-theme knobs around them are lib.mkDefault — override
|
||||||
# those with a plain home.nix assignment. See docs/OVERRIDES.md.
|
# those with a plain home.nix assignment. See docs/OVERRIDES.md.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# hyprlock + hypridle — screen locking and idle management, themed from
|
# hyprlock + hypridle — screen locking and idle management, themed from
|
||||||
# theme-state.json. One concern, one file: hypridle drives WHEN (idle
|
# state.json. One concern, one file: hypridle drives WHEN (idle
|
||||||
# lock, display off, suspend, lock-before-sleep), hyprlock is the
|
# lock, display off, suspend, lock-before-sleep), hyprlock is the
|
||||||
# themed lock screen itself (also behind the power menu's Lock entry).
|
# themed lock screen itself (also behind the power menu's Lock entry).
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
# home-manager switch (progress via notify-send).
|
# home-manager switch (progress via notify-send).
|
||||||
{ mods = "$mod"; key = "T"; action = "exec, nomarchy-menu theme"; desc = "Theme picker"; group = "Menu"; }
|
{ mods = "$mod"; key = "T"; action = "exec, nomarchy-menu theme"; desc = "Theme picker"; group = "Menu"; }
|
||||||
# Cycle the current theme's wallpapers (instant, no rebuild).
|
# Cycle the current theme's wallpapers (instant, no rebuild).
|
||||||
{ mods = "$mod SHIFT"; key = "T"; action = "exec, nomarchy-theme-sync bg next"; desc = "Next wallpaper"; group = "Menu"; }
|
{ mods = "$mod SHIFT"; key = "T"; action = "exec, nomarchy-state-sync bg next"; desc = "Next wallpaper"; group = "Menu"; }
|
||||||
|
|
||||||
# Power menu via the dispatcher. Not Escape: Super+Escape gets
|
# Power menu via the dispatcher. Not Escape: Super+Escape gets
|
||||||
# swallowed before reaching the dispatcher on some setups.
|
# swallowed before reaching the dispatcher on some setups.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Kitty — Nomarchy's only terminal, themed from theme-state.json.
|
# Kitty — Nomarchy's only terminal, themed from state.json.
|
||||||
# Colors, fonts and the full 16-color ANSI palette are baked from the
|
# Colors, fonts and the full 16-color ANSI palette are baked from the
|
||||||
# JSON at eval time (same contract Ghostty used to have). Always
|
# JSON at eval time (same contract Ghostty used to have). Always
|
||||||
# installed: SUPER+Return, SUPER+E, doctor, calendar, and $TERMINAL
|
# installed: SUPER+Return, SUPER+E, doctor, calendar, and $TERMINAL
|
||||||
|
|||||||
@@ -88,11 +88,11 @@ in
|
|||||||
# ── Required ───────────────────────────────────────────────────
|
# ── Required ───────────────────────────────────────────────────
|
||||||
stateFile = lib.mkOption {
|
stateFile = lib.mkOption {
|
||||||
type = lib.types.path;
|
type = lib.types.path;
|
||||||
example = lib.literalExpression "./theme-state.json";
|
example = lib.literalExpression "./state.json";
|
||||||
description = ''
|
description = ''
|
||||||
Path to theme-state.json, the single source of truth for all UI
|
Path to state.json, the single source of truth for all UI
|
||||||
configuration. Must live inside your flake (so evaluation stays
|
configuration. Must live inside your flake (so evaluation stays
|
||||||
pure) and be git-tracked. nomarchy-theme-sync writes to the
|
pure) and be git-tracked. nomarchy-state-sync writes to the
|
||||||
on-disk copy; rebuilds bake it into the generation.
|
on-disk copy; rebuilds bake it into the generation.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
@@ -182,9 +182,9 @@ in
|
|||||||
|
|
||||||
package = lib.mkOption {
|
package = lib.mkOption {
|
||||||
type = lib.types.package;
|
type = lib.types.package;
|
||||||
default = pkgs.nomarchy-theme-sync;
|
default = pkgs.nomarchy-state-sync;
|
||||||
defaultText = lib.literalExpression "pkgs.nomarchy-theme-sync";
|
defaultText = lib.literalExpression "pkgs.nomarchy-state-sync";
|
||||||
description = "The nomarchy-theme-sync package (provided by overlays.default).";
|
description = "The nomarchy-state-sync package (provided by overlays.default).";
|
||||||
};
|
};
|
||||||
|
|
||||||
themesDir = lib.mkOption {
|
themesDir = lib.mkOption {
|
||||||
@@ -379,7 +379,7 @@ in
|
|||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = config.nomarchy.settings.fingerprint.pam or false;
|
default = config.nomarchy.settings.fingerprint.pam or false;
|
||||||
defaultText = lib.literalExpression
|
defaultText = lib.literalExpression
|
||||||
"(settings.fingerprint.pam from theme-state.json) or false";
|
"(settings.fingerprint.pam from state.json) or false";
|
||||||
description = ''
|
description = ''
|
||||||
Unlock the lock screen with a fingerprint as well as the password, and
|
Unlock the lock screen with a fingerprint as well as the password, and
|
||||||
say so on the input field.
|
say so on the input field.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# lock). The media keys drive it via swayosd-client (it performs the
|
# lock). The media keys drive it via swayosd-client (it performs the
|
||||||
# action AND shows the OSD), so changing volume or brightness gives the
|
# action AND shows the OSD), so changing volume or brightness gives the
|
||||||
# visual feedback that bare wpctl/brightnessctl didn't. Themed from
|
# visual feedback that bare wpctl/brightnessctl didn't. Themed from
|
||||||
# theme-state.json. Brightness needs the backlight udev rule shipped
|
# state.json. Brightness needs the backlight udev rule shipped
|
||||||
# system-side (modules/nixos/default.nix → services.udev.packages).
|
# system-side (modules/nixos/default.nix → services.udev.packages).
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# rofi (2.0, native Wayland on 26.05) — launcher + dmenu renderer for
|
# rofi (2.0, native Wayland on 26.05) — launcher + dmenu renderer for
|
||||||
# the menu system, themed from theme-state.json. rofi's .rasi is far more
|
# the menu system, themed from state.json. rofi's .rasi is far more
|
||||||
# expressive than a flat scheme, so the generated theme styles each
|
# expressive than a flat scheme, so the generated theme styles each
|
||||||
# element (accent border, highlighted selection, rounded inputbar); a
|
# element (accent border, highlighted selection, rounded inputbar); a
|
||||||
# themes/<slug>/rofi.rasi whole-swap replaces it entirely.
|
# themes/<slug>/rofi.rasi whole-swap replaces it entirely.
|
||||||
@@ -71,7 +71,7 @@ let
|
|||||||
|
|
||||||
# One grid row per theme (square thumb + name, ✓ on the active one), and a
|
# One grid row per theme (square thumb + name, ✓ on the active one), and a
|
||||||
# Name→slug map so the picked label resolves back to the preset that
|
# Name→slug map so the picked label resolves back to the preset that
|
||||||
# nomarchy-theme-sync applies (pretty names ≠ slugs, hence the map). Themes
|
# nomarchy-state-sync applies (pretty names ≠ slugs, hence the map). Themes
|
||||||
# with no preview degrade to a plain-name row.
|
# with no preview degrade to a plain-name row.
|
||||||
themeRows = lib.concatMapStringsSep "\n" (th:
|
themeRows = lib.concatMapStringsSep "\n" (th:
|
||||||
let label = th.name + lib.optionalString (th.slug == t.slug) " ✓";
|
let label = th.name + lib.optionalString (th.slug == t.slug) " ✓";
|
||||||
@@ -190,7 +190,7 @@ let
|
|||||||
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
|
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
|
||||||
# Nomarchy menu dispatcher — thin presentation layer over
|
# Nomarchy menu dispatcher — thin presentation layer over
|
||||||
# `rofi -dmenu` (via rofi_menu helper); actions delegate to
|
# `rofi -dmenu` (via rofi_menu helper); actions delegate to
|
||||||
# nomarchy-theme-sync, systemctl, hyprctl and friends. Menu entries
|
# nomarchy-state-sync, systemctl, hyprctl and friends. Menu entries
|
||||||
# carry real icons from the theme's icon set (Papirus) via rofi's
|
# carry real icons from the theme's icon set (Papirus) via rofi's
|
||||||
# per-row icon protocol — see row() below. `nomarchy-menu` with no
|
# per-row icon protocol — see row() below. `nomarchy-menu` with no
|
||||||
# argument shows the module picker.
|
# argument shows the module picker.
|
||||||
@@ -350,7 +350,7 @@ let
|
|||||||
# power-profile icons (the two granular System ▸ Power profile /
|
# power-profile icons (the two granular System ▸ Power profile /
|
||||||
# Battery limit rows stay for direct access). No new backend: profile
|
# Battery limit rows stay for direct access). No new backend: profile
|
||||||
# rows drive powerprofilesctl like the power-profile picker, charge
|
# rows drive powerprofilesctl like the power-profile picker, charge
|
||||||
# rows drive the same theme-sync writer as batterylimit. Each half
|
# rows drive the same state-sync writer as batterylimit. Each half
|
||||||
# self-gates on its hardware, so a desktop with neither just toasts.
|
# self-gates on its hardware, so a desktop with neither just toasts.
|
||||||
haveppd=false; command -v powerprofilesctl >/dev/null 2>&1 && haveppd=true
|
haveppd=false; command -v powerprofilesctl >/dev/null 2>&1 && haveppd=true
|
||||||
havecharge=false; has_charge_threshold && havecharge=true
|
havecharge=false; has_charge_threshold && havecharge=true
|
||||||
@@ -359,7 +359,7 @@ let
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
curp=""; $haveppd && curp=$(powerprofilesctl get 2>/dev/null)
|
curp=""; $haveppd && curp=$(powerprofilesctl get 2>/dev/null)
|
||||||
curc=$(nomarchy-theme-sync get settings.power.batteryChargeLimit 2>/dev/null)
|
curc=$(nomarchy-state-sync get settings.power.batteryChargeLimit 2>/dev/null)
|
||||||
choice=$( {
|
choice=$( {
|
||||||
if $haveppd; then
|
if $haveppd; then
|
||||||
powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p' \
|
powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p' \
|
||||||
@@ -384,7 +384,7 @@ let
|
|||||||
# allows users to restart it) so a non-writable sysfs node still
|
# allows users to restart it) so a non-writable sysfs node still
|
||||||
# updates. Fallback: direct echo if the node is group-writable.
|
# updates. Fallback: direct echo if the node is group-writable.
|
||||||
set_limit() {
|
set_limit() {
|
||||||
nomarchy-theme-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch
|
nomarchy-state-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch
|
||||||
n="$1"; [ "$n" = null ] && n=100
|
n="$1"; [ "$n" = null ] && n=100
|
||||||
# Root oneshot sets Custom charge type (Dell) + thresholds.
|
# Root oneshot sets Custom charge type (Dell) + thresholds.
|
||||||
applied=
|
applied=
|
||||||
@@ -452,7 +452,7 @@ ${themeRows}
|
|||||||
[ "$choice" = "$BACK" ] && exec "$0"
|
[ "$choice" = "$BACK" ] && exec "$0"
|
||||||
choice="''${choice% ✓}" # drop the active marker if present
|
choice="''${choice% ✓}" # drop the active marker if present
|
||||||
slug="''${THEME_SLUG[$choice]:-}"
|
slug="''${THEME_SLUG[$choice]:-}"
|
||||||
[ -n "$slug" ] && exec nomarchy-theme-sync apply "$slug" ;;
|
[ -n "$slug" ] && exec nomarchy-state-sync apply "$slug" ;;
|
||||||
|
|
||||||
clipboard)
|
clipboard)
|
||||||
sel=$( { cliphist list; printf '%s\n' "$BACK"; } | rofi_menu -p clip) || exit 0
|
sel=$( { cliphist list; printf '%s\n' "$BACK"; } | rofi_menu -p clip) || exit 0
|
||||||
@@ -691,7 +691,7 @@ ${themeRows}
|
|||||||
pos=$(printf '%s' "$mons" | jq -r --arg n "$name" '.[]|select(.name==$n)|"\(.x)x\(.y)"')
|
pos=$(printf '%s' "$mons" | jq -r --arg n "$name" '.[]|select(.name==$n)|"\(.x)x\(.y)"')
|
||||||
scale=$(printf '%s' "$mons" | jq -r --arg n "$name" '.[]|select(.name==$n)|.scale')
|
scale=$(printf '%s' "$mons" | jq -r --arg n "$name" '.[]|select(.name==$n)|.scale')
|
||||||
if hyprctl keyword monitor "$name,$mode,$pos,$scale" >/dev/null 2>&1; then
|
if hyprctl keyword monitor "$name,$mode,$pos,$scale" >/dev/null 2>&1; then
|
||||||
nomarchy-theme-sync --quiet set "settings.monitors.$name" "$mode" --no-switch
|
nomarchy-state-sync --quiet set "settings.monitors.$name" "$mode" --no-switch
|
||||||
got=$(hyprctl monitors -j | jq -r --arg n "$name" \
|
got=$(hyprctl monitors -j | jq -r --arg n "$name" \
|
||||||
'.[]|select(.name==$n)|"\(.width)x\(.height)@\(.refreshRate|round)"')
|
'.[]|select(.name==$n)|"\(.width)x\(.height)@\(.refreshRate|round)"')
|
||||||
notify-send "Display" "$name → $mode (now ${got:-?})"
|
notify-send "Display" "$name → $mode (now ${got:-?})"
|
||||||
@@ -759,7 +759,7 @@ ${themeRows}
|
|||||||
# Power / Keybindings). SUPER+T / SUPER+SHIFT+T still hit leaves.
|
# Power / Keybindings). SUPER+T / SUPER+SHIFT+T still hit leaves.
|
||||||
choice=$( {
|
choice=$( {
|
||||||
row "Theme${menuHint "theme"}" preferences-desktop-theme
|
row "Theme${menuHint "theme"}" preferences-desktop-theme
|
||||||
row "Next wallpaper${hintForAction "exec, nomarchy-theme-sync bg next"}" preferences-desktop-wallpaper
|
row "Next wallpaper${hintForAction "exec, nomarchy-state-sync bg next"}" preferences-desktop-wallpaper
|
||||||
row "Reset wallpaper (auto)" preferences-desktop-wallpaper
|
row "Reset wallpaper (auto)" preferences-desktop-wallpaper
|
||||||
# Use the toggle script (knows hyprsunset vs wlsunset); a hard-coded
|
# Use the toggle script (knows hyprsunset vs wlsunset); a hard-coded
|
||||||
# hyprsunset-only check always showed (off) in geo mode and when
|
# hyprsunset-only check always showed (off) in geo mode and when
|
||||||
@@ -768,7 +768,7 @@ ${themeRows}
|
|||||||
then row "Night light (on)" weather-clear-night
|
then row "Night light (on)" weather-clear-night
|
||||||
else row "Night light (off)" weather-clear-night
|
else row "Night light (off)" weather-clear-night
|
||||||
fi
|
fi
|
||||||
if [ "$(nomarchy-theme-sync get settings.autoTheme.enable 2>/dev/null)" = true ]
|
if [ "$(nomarchy-state-sync get settings.autoTheme.enable 2>/dev/null)" = true ]
|
||||||
then row "Auto theme (on)" preferences-desktop-theme
|
then row "Auto theme (on)" preferences-desktop-theme
|
||||||
else row "Auto theme (off)" preferences-desktop-theme
|
else row "Auto theme (off)" preferences-desktop-theme
|
||||||
fi
|
fi
|
||||||
@@ -778,8 +778,8 @@ ${themeRows}
|
|||||||
"$BACK") exec "$0" ;;
|
"$BACK") exec "$0" ;;
|
||||||
*"Auto theme"*) exec "$0" autotheme ;;
|
*"Auto theme"*) exec "$0" autotheme ;;
|
||||||
*Theme*) exec "$0" theme ;;
|
*Theme*) exec "$0" theme ;;
|
||||||
*"Next wallpaper"*) exec nomarchy-theme-sync bg next ;;
|
*"Next wallpaper"*) exec nomarchy-state-sync bg next ;;
|
||||||
*"Reset wallpaper"*) exec nomarchy-theme-sync bg auto ;;
|
*"Reset wallpaper"*) exec nomarchy-state-sync bg auto ;;
|
||||||
*"Night light"*) exec "$0" nightlight ;;
|
*"Night light"*) exec "$0" nightlight ;;
|
||||||
esac ;;
|
esac ;;
|
||||||
|
|
||||||
@@ -796,7 +796,7 @@ ${themeRows}
|
|||||||
if [ "$p" = "$active" ]; then printf '● %s\n' "$p"; else printf '○ %s\n' "$p"; fi
|
if [ "$p" = "$active" ]; then printf '● %s\n' "$p"; else printf '○ %s\n' "$p"; fi
|
||||||
done
|
done
|
||||||
printf 'Base layout\n'
|
printf 'Base layout\n'
|
||||||
if [ "$(nomarchy-theme-sync get settings.displayProfileAuto 2>/dev/null)" = true ]
|
if [ "$(nomarchy-state-sync get settings.displayProfileAuto 2>/dev/null)" = true ]
|
||||||
then printf 'Auto-switch (on)\n'
|
then printf 'Auto-switch (on)\n'
|
||||||
else printf 'Auto-switch (off)\n'
|
else printf 'Auto-switch (off)\n'
|
||||||
fi
|
fi
|
||||||
@@ -808,11 +808,11 @@ ${themeRows}
|
|||||||
*"Auto-switch"*)
|
*"Auto-switch"*)
|
||||||
# Instant in-flake flag; the watcher reads it live on the next
|
# Instant in-flake flag; the watcher reads it live on the next
|
||||||
# output change — no rebuild, no unit juggling.
|
# output change — no rebuild, no unit juggling.
|
||||||
if [ "$(nomarchy-theme-sync get settings.displayProfileAuto 2>/dev/null)" = true ]; then
|
if [ "$(nomarchy-state-sync get settings.displayProfileAuto 2>/dev/null)" = true ]; then
|
||||||
nomarchy-theme-sync --quiet set settings.displayProfileAuto false --no-switch
|
nomarchy-state-sync --quiet set settings.displayProfileAuto false --no-switch
|
||||||
notify-send "Display profiles" "Auto-switch off — profiles change only from this menu."
|
notify-send "Display profiles" "Auto-switch off — profiles change only from this menu."
|
||||||
else
|
else
|
||||||
nomarchy-theme-sync --quiet set settings.displayProfileAuto true --no-switch
|
nomarchy-state-sync --quiet set settings.displayProfileAuto true --no-switch
|
||||||
notify-send "Display profiles" "Auto-switch on — plugging/unplugging outputs picks the matching profile."
|
notify-send "Display profiles" "Auto-switch on — plugging/unplugging outputs picks the matching profile."
|
||||||
fi
|
fi
|
||||||
exec "$0" display-profile ;;
|
exec "$0" display-profile ;;
|
||||||
@@ -869,12 +869,12 @@ ${themeRows}
|
|||||||
# (settings.autoTheme, applied by the nomarchy-auto-theme timer from
|
# (settings.autoTheme, applied by the nomarchy-auto-theme timer from
|
||||||
# autotheme.nix). The enable flag GATES the timer's install, so
|
# autotheme.nix). The enable flag GATES the timer's install, so
|
||||||
# turning it *on* must rebuild; day/night/times are read live by
|
# turning it *on* must rebuild; day/night/times are read live by
|
||||||
# `nomarchy-theme-sync auto`, so those writes are instant
|
# `nomarchy-state-sync auto`, so those writes are instant
|
||||||
# (--no-switch). Enabling writes the flag then `auto --force`, so a
|
# (--no-switch). Enabling writes the flag then `auto --force`, so a
|
||||||
# single rebuild both installs the timer and applies the right theme
|
# single rebuild both installs the timer and applies the right theme
|
||||||
# now. Disabling is instant: the flag flips and `auto` self-gates to
|
# now. Disabling is instant: the flag flips and `auto` self-gates to
|
||||||
# a no-op, so the lingering timer does nothing until the next rebuild.
|
# a no-op, so the lingering timer does nothing until the next rebuild.
|
||||||
at_get() { nomarchy-theme-sync get "settings.autoTheme.$1" 2>/dev/null; }
|
at_get() { nomarchy-state-sync get "settings.autoTheme.$1" 2>/dev/null; }
|
||||||
en=$(at_get enable); day=$(at_get day); night=$(at_get night)
|
en=$(at_get enable); day=$(at_get day); night=$(at_get night)
|
||||||
sunrise=$(at_get sunrise); sunset=$(at_get sunset)
|
sunrise=$(at_get sunrise); sunset=$(at_get sunset)
|
||||||
choice=$( {
|
choice=$( {
|
||||||
@@ -892,25 +892,25 @@ ${themeRows}
|
|||||||
"$BACK") exec "$0" lookfeel ;;
|
"$BACK") exec "$0" lookfeel ;;
|
||||||
*"Auto theme"*)
|
*"Auto theme"*)
|
||||||
if [ "$en" = true ]; then
|
if [ "$en" = true ]; then
|
||||||
nomarchy-theme-sync --quiet set settings.autoTheme.enable false --no-switch
|
nomarchy-state-sync --quiet set settings.autoTheme.enable false --no-switch
|
||||||
notify-send "Auto theme" "Off — current theme stays."
|
notify-send "Auto theme" "Off — current theme stays."
|
||||||
exec "$0" autotheme
|
exec "$0" autotheme
|
||||||
fi
|
fi
|
||||||
# Need a day + night pair before enabling; default the summer pair.
|
# Need a day + night pair before enabling; default the summer pair.
|
||||||
[ -n "$day" ] || nomarchy-theme-sync --quiet set settings.autoTheme.day summer-day --no-switch
|
[ -n "$day" ] || nomarchy-state-sync --quiet set settings.autoTheme.day summer-day --no-switch
|
||||||
[ -n "$night" ] || nomarchy-theme-sync --quiet set settings.autoTheme.night summer-night --no-switch
|
[ -n "$night" ] || nomarchy-state-sync --quiet set settings.autoTheme.night summer-night --no-switch
|
||||||
nomarchy-theme-sync --quiet set settings.autoTheme.enable true --no-switch
|
nomarchy-state-sync --quiet set settings.autoTheme.enable true --no-switch
|
||||||
notify-send "Auto theme" "On — applying the theme for now…"
|
notify-send "Auto theme" "On — applying the theme for now…"
|
||||||
exec nomarchy-theme-sync auto --force ;; # one rebuild: install timer + apply
|
exec nomarchy-state-sync auto --force ;; # one rebuild: install timer + apply
|
||||||
"Day theme"*)
|
"Day theme"*)
|
||||||
slug=$( { nomarchy-theme-sync list; printf '%s\n' "$BACK"; } | rofi_menu -p "Day theme") || exec "$0" autotheme
|
slug=$( { nomarchy-state-sync list; printf '%s\n' "$BACK"; } | rofi_menu -p "Day theme") || exec "$0" autotheme
|
||||||
[ "$slug" != "$BACK" ] && [ -n "$slug" ] \
|
[ "$slug" != "$BACK" ] && [ -n "$slug" ] \
|
||||||
&& nomarchy-theme-sync --quiet set settings.autoTheme.day "$slug" --no-switch
|
&& nomarchy-state-sync --quiet set settings.autoTheme.day "$slug" --no-switch
|
||||||
exec "$0" autotheme ;;
|
exec "$0" autotheme ;;
|
||||||
"Night theme"*)
|
"Night theme"*)
|
||||||
slug=$( { nomarchy-theme-sync list; printf '%s\n' "$BACK"; } | rofi_menu -p "Night theme") || exec "$0" autotheme
|
slug=$( { nomarchy-state-sync list; printf '%s\n' "$BACK"; } | rofi_menu -p "Night theme") || exec "$0" autotheme
|
||||||
[ "$slug" != "$BACK" ] && [ -n "$slug" ] \
|
[ "$slug" != "$BACK" ] && [ -n "$slug" ] \
|
||||||
&& nomarchy-theme-sync --quiet set settings.autoTheme.night "$slug" --no-switch
|
&& nomarchy-state-sync --quiet set settings.autoTheme.night "$slug" --no-switch
|
||||||
exec "$0" autotheme ;;
|
exec "$0" autotheme ;;
|
||||||
"Sunrise"*|"Sunset"*)
|
"Sunrise"*|"Sunset"*)
|
||||||
key=sunrise; label=Sunrise; cur="''${sunrise:-07:00}"
|
key=sunrise; label=Sunrise; cur="''${sunrise:-07:00}"
|
||||||
@@ -919,7 +919,7 @@ ${themeRows}
|
|||||||
case "$t" in
|
case "$t" in
|
||||||
""|"$BACK") : ;;
|
""|"$BACK") : ;;
|
||||||
[01][0-9]:[0-5][0-9]|2[0-3]:[0-5][0-9])
|
[01][0-9]:[0-5][0-9]|2[0-3]:[0-5][0-9])
|
||||||
nomarchy-theme-sync --quiet set "settings.autoTheme.$key" "$t" --no-switch ;;
|
nomarchy-state-sync --quiet set "settings.autoTheme.$key" "$t" --no-switch ;;
|
||||||
*) notify-send "Auto theme" "Ignored '$t' — use HH:MM (24-hour)." ;;
|
*) notify-send "Auto theme" "Ignored '$t' — use HH:MM (24-hour)." ;;
|
||||||
esac
|
esac
|
||||||
exec "$0" autotheme ;;
|
exec "$0" autotheme ;;
|
||||||
@@ -945,18 +945,18 @@ ${themeRows}
|
|||||||
|
|
||||||
autocommit)
|
autocommit)
|
||||||
# Toggle opt-in auto-commit: every menu/theme mutation also commits
|
# Toggle opt-in auto-commit: every menu/theme mutation also commits
|
||||||
# theme-state.json (that file only) in the downstream flake, so
|
# state.json (that file only) in the downstream flake, so
|
||||||
# settings history is `git log`; nomarchy-pull/-rebuild/-home
|
# settings history is `git log`; nomarchy-pull/-rebuild/-home
|
||||||
# additionally sweep everything else dirty (hand edits, lock bumps)
|
# additionally sweep everything else dirty (hand edits, lock bumps)
|
||||||
# into a commit first. Nothing in Nix consumes the flag —
|
# into a commit first. Nothing in Nix consumes the flag —
|
||||||
# the tool reads the live state on each write — so the toggle is
|
# the tool reads the live state on each write — so the toggle is
|
||||||
# instant, no rebuild. The off-write commits too (the tool fires
|
# instant, no rebuild. The off-write commits too (the tool fires
|
||||||
# when the flag was on before OR after), keeping history consistent.
|
# when the flag was on before OR after), keeping history consistent.
|
||||||
if [ "$(nomarchy-theme-sync get settings.autoCommit 2>/dev/null)" = true ]; then
|
if [ "$(nomarchy-state-sync get settings.autoCommit 2>/dev/null)" = true ]; then
|
||||||
nomarchy-theme-sync --quiet set settings.autoCommit false --no-switch
|
nomarchy-state-sync --quiet set settings.autoCommit false --no-switch
|
||||||
notify-send "Auto-commit" "Off — menu changes stay uncommitted in your flake."
|
notify-send "Auto-commit" "Off — menu changes stay uncommitted in your flake."
|
||||||
else
|
else
|
||||||
nomarchy-theme-sync --quiet set settings.autoCommit true --no-switch
|
nomarchy-state-sync --quiet set settings.autoCommit true --no-switch
|
||||||
notify-send "Auto-commit" "On — menu changes commit; rebuilds sweep pending edits in too."
|
notify-send "Auto-commit" "On — menu changes commit; rebuilds sweep pending edits in too."
|
||||||
fi
|
fi
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
@@ -1017,7 +1017,7 @@ ${themeRows}
|
|||||||
# CLI (services.fprintd). Enroll is interactive → terminal.
|
# CLI (services.fprintd). Enroll is interactive → terminal.
|
||||||
command -v fprintd-list >/dev/null 2>&1 \
|
command -v fprintd-list >/dev/null 2>&1 \
|
||||||
|| { notify-send "Fingerprint" "fprintd not available (nomarchy.hardware.fingerprint.enable?)."; exit 0; }
|
|| { notify-send "Fingerprint" "fprintd not available (nomarchy.hardware.fingerprint.enable?)."; exit 0; }
|
||||||
pam=$(nomarchy-theme-sync get settings.fingerprint.pam 2>/dev/null || echo false)
|
pam=$(nomarchy-state-sync get settings.fingerprint.pam 2>/dev/null || echo false)
|
||||||
case "$pam" in true|True) pam_label="Fingerprint (on)" ;; *) pam_label="Fingerprint (off)" ;; esac
|
case "$pam" in true|True) pam_label="Fingerprint (on)" ;; *) pam_label="Fingerprint (off)" ;; esac
|
||||||
choice=$( {
|
choice=$( {
|
||||||
# The switch leads: it is the decision this menu exists for, and
|
# The switch leads: it is the decision this menu exists for, and
|
||||||
@@ -1178,7 +1178,7 @@ ${themeRows}
|
|||||||
&& row "Keyboard${menuHint "keyboard"}" preferences-desktop-keyboard
|
&& row "Keyboard${menuHint "keyboard"}" preferences-desktop-keyboard
|
||||||
command -v system-config-printer >/dev/null 2>&1 && row "Printers" printer
|
command -v system-config-printer >/dev/null 2>&1 && row "Printers" printer
|
||||||
row "Do Not Disturb${menuHint "dnd"}" notification-disabled
|
row "Do Not Disturb${menuHint "dnd"}" notification-disabled
|
||||||
if [ "$(nomarchy-theme-sync get settings.autoTimezone 2>/dev/null)" = true ]
|
if [ "$(nomarchy-state-sync get settings.autoTimezone 2>/dev/null)" = true ]
|
||||||
then row "Auto timezone (on)" preferences-system-time
|
then row "Auto timezone (on)" preferences-system-time
|
||||||
else row "Auto timezone (off)" preferences-system-time
|
else row "Auto timezone (off)" preferences-system-time
|
||||||
fi
|
fi
|
||||||
@@ -1186,15 +1186,15 @@ ${themeRows}
|
|||||||
# greeter asks; that's the prompt the Fingerprint switch feeds.
|
# greeter asks; that's the prompt the Fingerprint switch feeds.
|
||||||
if command -v nomarchy-autologin >/dev/null 2>&1; then
|
if command -v nomarchy-autologin >/dev/null 2>&1; then
|
||||||
# Unset (get exits 1, empty) and null both read as off. "None" is
|
# Unset (get exits 1, empty) and null both read as off. "None" is
|
||||||
# what an older theme-sync printed for null — accepted so a menu
|
# what an older state-sync printed for null — accepted so a menu
|
||||||
# from a newer generation can't misreport against an older tool.
|
# from a newer generation can't misreport against an older tool.
|
||||||
case "$(nomarchy-theme-sync get settings.greeter.autoLogin 2>/dev/null)" in
|
case "$(nomarchy-state-sync get settings.greeter.autoLogin 2>/dev/null)" in
|
||||||
null|""|None) row "Auto-login (off)" system-users ;;
|
null|""|None) row "Auto-login (off)" system-users ;;
|
||||||
*) row "Auto-login (on)" system-users ;;
|
*) row "Auto-login (on)" system-users ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
if [ -e "''${NOMARCHY_PATH:-$HOME/.nomarchy}/.git" ]; then
|
if [ -e "''${NOMARCHY_PATH:-$HOME/.nomarchy}/.git" ]; then
|
||||||
if [ "$(nomarchy-theme-sync get settings.autoCommit 2>/dev/null)" = true ]
|
if [ "$(nomarchy-state-sync get settings.autoCommit 2>/dev/null)" = true ]
|
||||||
then row "Auto-commit (on)" git
|
then row "Auto-commit (on)" git
|
||||||
else row "Auto-commit (off)" git
|
else row "Auto-commit (off)" git
|
||||||
fi
|
fi
|
||||||
@@ -1255,7 +1255,7 @@ ${themeRows}
|
|||||||
|| { notify-send "Battery limit" \
|
|| { notify-send "Battery limit" \
|
||||||
"This machine's firmware does not expose a charge-stop control (no charge_control_end_threshold in /sys), so a charge limit cannot be set here. The battery still works normally — this is a hardware capability, not a Nomarchy problem."
|
"This machine's firmware does not expose a charge-stop control (no charge_control_end_threshold in /sys), so a charge limit cannot be set here. The battery still works normally — this is a hardware capability, not a Nomarchy problem."
|
||||||
exit 0; }
|
exit 0; }
|
||||||
cur=$(nomarchy-theme-sync get settings.power.batteryChargeLimit 2>/dev/null)
|
cur=$(nomarchy-state-sync get settings.power.batteryChargeLimit 2>/dev/null)
|
||||||
sel=$( {
|
sel=$( {
|
||||||
row "80% (recommended)" battery-080
|
row "80% (recommended)" battery-080
|
||||||
row "90%" battery-090
|
row "90%" battery-090
|
||||||
@@ -1265,7 +1265,7 @@ ${themeRows}
|
|||||||
back
|
back
|
||||||
} | rofi_menu -show-icons -p "Battery limit (now: ''${cur:-default})") || exit 0
|
} | rofi_menu -show-icons -p "Battery limit (now: ''${cur:-default})") || exit 0
|
||||||
set_limit() {
|
set_limit() {
|
||||||
nomarchy-theme-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch
|
nomarchy-state-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch
|
||||||
n="$1"; [ "$n" = null ] && n=100
|
n="$1"; [ "$n" = null ] && n=100
|
||||||
applied=
|
applied=
|
||||||
if systemctl restart nomarchy-battery-charge-limit.service 2>/dev/null; then
|
if systemctl restart nomarchy-battery-charge-limit.service 2>/dev/null; then
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Stylix — themes the long tail of applications (GTK, Qt, cursors,
|
# Stylix — themes the long tail of applications (GTK, Qt, cursors,
|
||||||
# fonts) from the same theme-state.json that drives the live engine.
|
# fonts) from the same state.json that drives the live engine.
|
||||||
#
|
#
|
||||||
# Division of labour: the hot-reload trio (Hyprland, Waybar, Kitty)
|
# Division of labour: the hot-reload trio (Hyprland, Waybar, Kitty)
|
||||||
# is owned by the Nomarchy engine and updates instantly; everything
|
# is owned by the Nomarchy engine and updates instantly; everything
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# swaync — notification daemon + control centre, themed from
|
# swaync — notification daemon + control centre, themed from
|
||||||
# theme-state.json. Until this shipped, nothing rendered notify-send at
|
# state.json. Until this shipped, nothing rendered notify-send at
|
||||||
# all: the theme-switch progress toasts, the CLI's font warnings and the
|
# all: the theme-switch progress toasts, the CLI's font warnings and the
|
||||||
# live ISO's welcome message were all invisible.
|
# live ISO's welcome message were all invisible.
|
||||||
{ config, lib, ... }:
|
{ config, lib, ... }:
|
||||||
@@ -29,7 +29,7 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
style = ''
|
style = ''
|
||||||
/* Palette baked from theme-state.json. Only roles guaranteed to
|
/* Palette baked from state.json. Only roles guaranteed to
|
||||||
contrast @base in EVERY palette are used: subtext/surface mean
|
contrast @base in EVERY palette are used: subtext/surface mean
|
||||||
"on-surface" in some light themes (summer-day: subtext==base,
|
"on-surface" in some light themes (summer-day: subtext==base,
|
||||||
surface==text — body text was invisible on hardware, item 25).
|
surface==text — body text was invisible on hardware, item 25).
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Nomarchy theming engine.
|
# Nomarchy theming engine.
|
||||||
#
|
#
|
||||||
# nomarchy.stateFile (theme-state.json, inside the consuming flake) is
|
# nomarchy.stateFile (state.json, inside the consuming flake) is
|
||||||
# ingested at evaluation time — pure, because flake files are store
|
# ingested at evaluation time — pure, because flake files are store
|
||||||
# paths — and exposed to every other module as `config.nomarchy.theme`.
|
# paths — and exposed to every other module as `config.nomarchy.theme`.
|
||||||
#
|
#
|
||||||
# Theme changes are fully Home Manager managed: `nomarchy-theme-sync
|
# Theme changes are fully Home Manager managed: `nomarchy-state-sync
|
||||||
# apply <theme>` writes the JSON and runs `home-manager switch`, baking
|
# apply <theme>` writes the JSON and runs `home-manager switch`, baking
|
||||||
# everything (Hyprland, Waybar, Kitty, btop, Stylix) into one
|
# everything (Hyprland, Waybar, Kitty, btop, Stylix) into one
|
||||||
# read-only generation. No runtime patching, no partial states; theme
|
# read-only generation. No runtime patching, no partial states; theme
|
||||||
@@ -13,17 +13,17 @@
|
|||||||
# The one runtime exception is the wallpaper (swww is imperative by
|
# The one runtime exception is the wallpaper (swww is imperative by
|
||||||
# nature): applied at session start, after every switch (hook below), and
|
# nature): applied at session start, after every switch (hook below), and
|
||||||
# after output hotplug (hyprland.nix); cycled instantly with
|
# after output hotplug (hyprland.nix); cycled instantly with
|
||||||
# `nomarchy-theme-sync bg next`.
|
# `nomarchy-state-sync bg next`.
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.nomarchy;
|
cfg = config.nomarchy;
|
||||||
|
|
||||||
# Fail-closed load: missing / empty / non-object get a short pointer at
|
# Fail-closed load: missing / empty / non-object get a short pointer at
|
||||||
# the template + `nomarchy-theme-sync validate`, not a raw readFile stack
|
# the template + `nomarchy-state-sync validate`, not a raw readFile stack
|
||||||
# from deep inside a consumer (nightlight, hyprland, …). Field-level
|
# from deep inside a consumer (nightlight, hyprland, …). Field-level
|
||||||
# checks below still run on the merged result.
|
# checks below still run on the merged result.
|
||||||
themeState = import ../theme-state-read.nix { inherit lib; } cfg.stateFile;
|
themeState = import ../state-read.nix { inherit lib; } cfg.stateFile;
|
||||||
|
|
||||||
# Defaults guarantee evaluation succeeds on a sparse or older state
|
# Defaults guarantee evaluation succeeds on a sparse or older state
|
||||||
# file (e.g. one written before a schema field was added). The shipped
|
# file (e.g. one written before a schema field was added). The shipped
|
||||||
@@ -92,7 +92,7 @@ let
|
|||||||
parsed = lib.recursiveUpdate defaults themeState;
|
parsed = lib.recursiveUpdate defaults themeState;
|
||||||
|
|
||||||
# ── Friendly eval-time validation ───────────────────────────────────
|
# ── Friendly eval-time validation ───────────────────────────────────
|
||||||
# The same schema nomarchy-theme-sync enforces before every write. A
|
# The same schema nomarchy-state-sync enforces before every write. A
|
||||||
# HAND-edited state file (the one path that bypasses the tool) must
|
# HAND-edited state file (the one path that bypasses the tool) must
|
||||||
# fail with the field, the problem, and the fix — not a Nix stack
|
# fail with the field, the problem, and the fix — not a Nix stack
|
||||||
# trace deep in some consumer. Checks run on `parsed` (after the
|
# trace deep in some consumer. Checks run on `parsed` (after the
|
||||||
@@ -137,14 +137,14 @@ let
|
|||||||
if problems == [ ] then parsed
|
if problems == [ ] then parsed
|
||||||
else throw ''
|
else throw ''
|
||||||
|
|
||||||
Nomarchy: your theme-state.json is invalid:
|
Nomarchy: your state.json is invalid:
|
||||||
${lib.concatMapStrings (p: " ✖ ${p}\n") problems}
|
${lib.concatMapStrings (p: " ✖ ${p}\n") problems}
|
||||||
Fix the named field(s) in the theme-state.json of your flake
|
Fix the named field(s) in the state.json of your flake
|
||||||
checkout (usually ~/.nomarchy/theme-state.json — the store copy at
|
checkout (usually ~/.nomarchy/state.json — the store copy at
|
||||||
${toString cfg.stateFile} is a snapshot of it). The tool prints
|
${toString cfg.stateFile} is a snapshot of it). The tool prints
|
||||||
the same report with per-field fixes: `nomarchy-theme-sync
|
the same report with per-field fixes: `nomarchy-state-sync
|
||||||
validate`. Re-applying any preset resets all appearance fields:
|
validate`. Re-applying any preset resets all appearance fields:
|
||||||
`nomarchy-theme-sync apply boreal`.'';
|
`nomarchy-state-sync apply boreal`.'';
|
||||||
|
|
||||||
# A border value is a palette key (look it up in colors) unless it's
|
# A border value is a palette key (look it up in colors) unless it's
|
||||||
# already a literal hex. Unknown roles are rejected by the field checks
|
# already a literal hex. Unknown roles are rejected by the field checks
|
||||||
@@ -155,7 +155,7 @@ let
|
|||||||
|
|
||||||
Nomarchy: border role "${v}" is not in the palette (colors.*).
|
Nomarchy: border role "${v}" is not in the palette (colors.*).
|
||||||
Use one of: ${lib.concatStringsSep ", " colorRoles}
|
Use one of: ${lib.concatStringsSep ", " colorRoles}
|
||||||
or a literal "#RRGGBB". Validate with: nomarchy-theme-sync validate'');
|
or a literal "#RRGGBB". Validate with: nomarchy-state-sync validate'');
|
||||||
border = {
|
border = {
|
||||||
active = resolveColor checked.border.active;
|
active = resolveColor checked.border.active;
|
||||||
inactive = resolveColor checked.border.inactive;
|
inactive = resolveColor checked.border.inactive;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# timezone. Waybar's clock module captures the zone once at construction, so a
|
# timezone. Waybar's clock module captures the zone once at construction, so a
|
||||||
# runtime timezone change (automatic-timezoned, nomarchy.system.autoTimezone)
|
# runtime timezone change (automatic-timezoned, nomarchy.system.autoTimezone)
|
||||||
# would NOT show until a relogin. A tiny watcher subscribes to timedate1's
|
# would NOT show until a relogin. A tiny watcher subscribes to timedate1's
|
||||||
# change signal and reloads Waybar (SIGUSR2 = the same reload theme-sync uses),
|
# change signal and reloads Waybar (SIGUSR2 = the same reload state-sync uses),
|
||||||
# so the clock follows your location live. Also catches a manual
|
# so the clock follows your location live. Also catches a manual
|
||||||
# `timedatectl set-timezone`.
|
# `timedatectl set-timezone`.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Waybar — two-tier theming:
|
# Waybar — two-tier theming:
|
||||||
#
|
#
|
||||||
# 1. Default: structure, fonts, geometry AND palette baked from
|
# 1. Default: structure, fonts, geometry AND palette baked from
|
||||||
# theme-state.json (colors as GTK named colors, @define-color).
|
# state.json (colors as GTK named colors, @define-color).
|
||||||
#
|
#
|
||||||
# 2. Whole-swap: themes with their own visual identity ship
|
# 2. Whole-swap: themes with their own visual identity ship
|
||||||
# <themesDir>/<slug>/waybar.css (and optionally waybar.jsonc) which
|
# <themesDir>/<slug>/waybar.css (and optionally waybar.jsonc) which
|
||||||
@@ -35,7 +35,7 @@ let
|
|||||||
# Waybar supervisor — exec-once has no restart, so a crashed bar used to
|
# Waybar supervisor — exec-once has no restart, so a crashed bar used to
|
||||||
# leave the session bar-less until relogin (seen on hardware: a theme
|
# leave the session bar-less until relogin (seen on hardware: a theme
|
||||||
# switch crashed waybar mid-reload). Respawns on ANY exit — a plain
|
# switch crashed waybar mid-reload). Respawns on ANY exit — a plain
|
||||||
# `pkill -x waybar` is now a clean restart, which nomarchy-theme-sync
|
# `pkill -x waybar` is now a clean restart, which nomarchy-state-sync
|
||||||
# uses instead of the crash-prone in-place SIGUSR2 reload when it sees
|
# uses instead of the crash-prone in-place SIGUSR2 reload when it sees
|
||||||
# this supervisor running. Crash-loop guard: 5 exits within 10s of
|
# this supervisor running. Crash-loop guard: 5 exits within 10s of
|
||||||
# their start → give up with a critical notification instead of
|
# their start → give up with a critical notification instead of
|
||||||
@@ -377,7 +377,7 @@ let
|
|||||||
};
|
};
|
||||||
|
|
||||||
generatedStyle = ''
|
generatedStyle = ''
|
||||||
/* Palette baked from theme-state.json */
|
/* Palette baked from state.json */
|
||||||
${colorDefs}
|
${colorDefs}
|
||||||
|
|
||||||
/* NB: this `*` reset reaches the SNI tray menus Waybar hosts too
|
/* NB: this `*` reset reaches the SNI tray menus Waybar hosts too
|
||||||
@@ -522,7 +522,7 @@ in
|
|||||||
# on a warm relogin — it started before the socket was up, exited, landed
|
# on a warm relogin — it started before the socket was up, exited, landed
|
||||||
# in `failed`, and was never retried, so the bar vanished. exec-once only
|
# in `failed`, and was never retried, so the bar vanished. exec-once only
|
||||||
# fires once Hyprland is up, dodging the race; theme switches reload the
|
# fires once Hyprland is up, dodging the race; theme switches reload the
|
||||||
# running bar via SIGUSR2 (nomarchy-theme-sync). No uwsm here to manage the
|
# running bar via SIGUSR2 (nomarchy-state-sync). No uwsm here to manage the
|
||||||
# session target, so we don't depend on its lifecycle.
|
# session target, so we don't depend on its lifecycle.
|
||||||
systemd.enable = false;
|
systemd.enable = false;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# yazi — the flagship file manager: a fast, keyboard-driven TUI that
|
# yazi — the flagship file manager: a fast, keyboard-driven TUI that
|
||||||
# fits the distro's identity (Kitty graphics previews, rofi
|
# fits the distro's identity (Kitty graphics previews, rofi
|
||||||
# menus, everything from one JSON). Themed from theme-state.json and
|
# menus, everything from one JSON). Themed from state.json and
|
||||||
# shipped with a curated plugin set. The GUI half (Thunar, "open folder"
|
# shipped with a curated plugin set. The GUI half (Thunar, "open folder"
|
||||||
# handler) is nomarchy.system.fileManager on the system side.
|
# handler) is nomarchy.system.fileManager on the system side.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ let
|
|||||||
# Control Center's Bluetooth toggle writes settings.bluetooth.enable; read
|
# Control Center's Bluetooth toggle writes settings.bluetooth.enable; read
|
||||||
# it from the state file, the only place it exists on the NixOS side (the
|
# it from the state file, the only place it exists on the NixOS side (the
|
||||||
# hardware.nix/timezone.nix bridge). Missing/invalid JSON fails closed via
|
# hardware.nix/timezone.nix bridge). Missing/invalid JSON fails closed via
|
||||||
# theme-state-read.nix rather than a raw stack. null = key absent, which
|
# state-read.nix rather than a raw stack. null = key absent, which
|
||||||
# leaves the option's own default (true) alone.
|
# leaves the option's own default (true) alone.
|
||||||
sysState =
|
sysState =
|
||||||
if cfg.stateFile != null
|
if cfg.stateFile != null
|
||||||
then import ../theme-state-read.nix { inherit lib; } cfg.stateFile
|
then import ../state-read.nix { inherit lib; } cfg.stateFile
|
||||||
else { };
|
else { };
|
||||||
stateBluetooth =
|
stateBluetooth =
|
||||||
let v = (sysState.settings or { }).bluetooth.enable or null;
|
let v = (sysState.settings or { }).bluetooth.enable or null;
|
||||||
@@ -70,7 +70,7 @@ in
|
|||||||
nomarchy-pull update flake inputs (nomarchy, nixpkgs, …)
|
nomarchy-pull update flake inputs (nomarchy, nixpkgs, …)
|
||||||
nomarchy-rebuild rebuild the system (current lock)
|
nomarchy-rebuild rebuild the system (current lock)
|
||||||
nomarchy-home rebuild the desktop / Home Manager
|
nomarchy-home rebuild the desktop / Home Manager
|
||||||
nomarchy-theme-sync apply <theme> switch the whole palette
|
nomarchy-state-sync apply <theme> switch the whole palette
|
||||||
nomarchy-doctor read-only health check
|
nomarchy-doctor read-only health check
|
||||||
SUPER+? keybindings cheatsheet
|
SUPER+? keybindings cheatsheet
|
||||||
SUPER+M → System › Firmware check LVFS firmware updates (fwupd)
|
SUPER+M → System › Firmware check LVFS firmware updates (fwupd)
|
||||||
@@ -336,7 +336,7 @@ in
|
|||||||
# ── Fonts ────────────────────────────────────────────────────────
|
# ── Fonts ────────────────────────────────────────────────────────
|
||||||
# The ten most popular Nerd Fonts ship by default, so any of them
|
# The ten most popular Nerd Fonts ship by default, so any of them
|
||||||
# can be named in the theme state's fonts.mono and actually resolve
|
# can be named in the theme state's fonts.mono and actually resolve
|
||||||
# (nomarchy-theme-sync warns when a configured font is missing).
|
# (nomarchy-state-sync warns when a configured font is missing).
|
||||||
fonts = {
|
fonts = {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
nerd-fonts.jetbrains-mono
|
nerd-fonts.jetbrains-mono
|
||||||
@@ -373,7 +373,7 @@ in
|
|||||||
|
|
||||||
# ── Essential packages ───────────────────────────────────────────
|
# ── Essential packages ───────────────────────────────────────────
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
nomarchy-theme-sync # provided by overlays.default
|
nomarchy-state-sync # provided by overlays.default
|
||||||
nomarchy-doctor # read-only health check (System › Doctor)
|
nomarchy-doctor # read-only health check (System › Doctor)
|
||||||
nomarchy-control-center # TUI control center
|
nomarchy-control-center # TUI control center
|
||||||
nomarchy-detect-hw # post-install hardware re-probe (HARDWARE.md §8)
|
nomarchy-detect-hw # post-install hardware re-probe (HARDWARE.md §8)
|
||||||
@@ -508,7 +508,7 @@ in
|
|||||||
experimental-features = [ "nix-command" "flakes" ];
|
experimental-features = [ "nix-command" "flakes" ];
|
||||||
auto-optimise-store = lib.mkDefault true;
|
auto-optimise-store = lib.mkDefault true;
|
||||||
# The downstream flake (~/.nomarchy) is meant to be a live working
|
# The downstream flake (~/.nomarchy) is meant to be a live working
|
||||||
# tree: nomarchy-theme-sync rewrites theme-state.json on every
|
# tree: nomarchy-state-sync rewrites state.json on every
|
||||||
# switch (and you needn't commit each tweak), so the "Git tree is
|
# switch (and you needn't commit each tweak), so the "Git tree is
|
||||||
# dirty" warning fires on every rebuild and is pure noise here.
|
# dirty" warning fires on every rebuild and is pure noise here.
|
||||||
warn-dirty = lib.mkDefault false;
|
warn-dirty = lib.mkDefault false;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Greeter — greetd/tuigreet, themed from the same theme-state.json that
|
# Greeter — greetd/tuigreet, themed from the same state.json that
|
||||||
# drives the desktop (nomarchy.system.stateFile; the Plymouth model:
|
# drives the desktop (nomarchy.system.stateFile; the Plymouth model:
|
||||||
# baked at SYSTEM rebuild, so it follows the theme as of the last
|
# baked at SYSTEM rebuild, so it follows the theme as of the last
|
||||||
# sys-update, not the last instant apply).
|
# sys-update, not the last instant apply).
|
||||||
@@ -27,7 +27,7 @@ let
|
|||||||
cfg = config.nomarchy.system;
|
cfg = config.nomarchy.system;
|
||||||
distroName = config.system.nixos.distroName;
|
distroName = config.system.nixos.distroName;
|
||||||
|
|
||||||
sync = lib.getExe pkgs.nomarchy-theme-sync;
|
sync = lib.getExe pkgs.nomarchy-state-sync;
|
||||||
|
|
||||||
# Menu/CLI toggle, same shape as nomarchy-autotimezone: runs as the normal
|
# Menu/CLI toggle, same shape as nomarchy-autotimezone: runs as the normal
|
||||||
# user (it owns the flake checkout + writes the state), sudos only the
|
# user (it owns the flake checkout + writes the state), sudos only the
|
||||||
@@ -62,12 +62,12 @@ let
|
|||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Fails closed with an actionable message via theme-state-read.nix, like
|
# Fails closed with an actionable message via state-read.nix, like
|
||||||
# every other stateFile consumer — a raw fromJSON here would bury a bad
|
# every other stateFile consumer — a raw fromJSON here would bury a bad
|
||||||
# state file under a Nix stack pointing at greeter.nix.
|
# state file under a Nix stack pointing at greeter.nix.
|
||||||
state =
|
state =
|
||||||
if cfg.stateFile != null
|
if cfg.stateFile != null
|
||||||
then import ../theme-state-read.nix { inherit lib; } cfg.stateFile
|
then import ../state-read.nix { inherit lib; } cfg.stateFile
|
||||||
else { };
|
else { };
|
||||||
|
|
||||||
# The auto-login user from the state, or null. Read here via the state file
|
# The auto-login user from the state, or null. Read here via the state file
|
||||||
|
|||||||
@@ -15,16 +15,16 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.nomarchy.hardware;
|
cfg = config.nomarchy.hardware;
|
||||||
# Fingerprint PAM can follow theme-state.json (menu toggle → next
|
# Fingerprint PAM can follow state.json (menu toggle → next
|
||||||
# sys-rebuild), same bridge as autoTimezone (BACKLOG #55). Missing or
|
# sys-rebuild), same bridge as autoTimezone (BACKLOG #55). Missing or
|
||||||
# invalid JSON fails closed (theme-state-read.nix) instead of a raw stack.
|
# invalid JSON fails closed (state-read.nix) instead of a raw stack.
|
||||||
hwState =
|
hwState =
|
||||||
if config.nomarchy.system.stateFile != null
|
if config.nomarchy.system.stateFile != null
|
||||||
then import ../theme-state-read.nix { inherit lib; } config.nomarchy.system.stateFile
|
then import ../state-read.nix { inherit lib; } config.nomarchy.system.stateFile
|
||||||
else { };
|
else { };
|
||||||
pamFromState = (hwState.settings or { }).fingerprint.pam or false;
|
pamFromState = (hwState.settings or { }).fingerprint.pam or false;
|
||||||
|
|
||||||
sync = lib.getExe pkgs.nomarchy-theme-sync;
|
sync = lib.getExe pkgs.nomarchy-state-sync;
|
||||||
|
|
||||||
# The single fingerprint on/off switch (System › Fingerprint). One state key
|
# The single fingerprint on/off switch (System › Fingerprint). One state key
|
||||||
# for one user-facing decision — it drives login/sudo PAM here AND the
|
# for one user-facing decision — it drives login/sudo PAM here AND the
|
||||||
@@ -156,11 +156,11 @@ in
|
|||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = pamFromState;
|
default = pamFromState;
|
||||||
defaultText = lib.literalExpression
|
defaultText = lib.literalExpression
|
||||||
"(settings.fingerprint.pam from theme-state.json) or false";
|
"(settings.fingerprint.pam from state.json) or false";
|
||||||
description = ''
|
description = ''
|
||||||
Use the fingerprint for login and sudo (PAM). Opt-in — password-only
|
Use the fingerprint for login and sudo (PAM). Opt-in — password-only
|
||||||
stays the default for the cautious; enroll a finger first. Defaults
|
stays the default for the cautious; enroll a finger first. Defaults
|
||||||
from theme-state.json `settings.fingerprint.pam` (System › Fingerprint
|
from state.json `settings.fingerprint.pam` (System › Fingerprint
|
||||||
menu) when set; otherwise false.
|
menu) when set; otherwise false.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
default = null;
|
default = null;
|
||||||
defaultText = lib.literalExpression
|
defaultText = lib.literalExpression
|
||||||
"(settings.greeter.autoLogin from theme-state.json) or null";
|
"(settings.greeter.autoLogin from state.json) or null";
|
||||||
example = "ada";
|
example = "ada";
|
||||||
description = ''
|
description = ''
|
||||||
Log this user straight into Hyprland on boot (greetd
|
Log this user straight into Hyprland on boot (greetd
|
||||||
initial_session); logging out lands on the normal greeter.
|
initial_session); logging out lands on the normal greeter.
|
||||||
|
|
||||||
Normally you leave this alone and use System › Auto-login, which
|
Normally you leave this alone and use System › Auto-login, which
|
||||||
writes `settings.greeter.autoLogin` in theme-state.json —
|
writes `settings.greeter.autoLogin` in state.json —
|
||||||
./greeter.nix mkDefaults this option from it. The installer seeds
|
./greeter.nix mkDefaults this option from it. The installer seeds
|
||||||
that state on LUKS-encrypted machines: the disk passphrase already
|
that state on LUKS-encrypted machines: the disk passphrase already
|
||||||
gates access, so a second prompt is ceremony. Setting this option by
|
gates access, so a second prompt is ceremony. Setting this option by
|
||||||
@@ -30,15 +30,15 @@
|
|||||||
|
|
||||||
plymouth.enable = lib.mkEnableOption ''
|
plymouth.enable = lib.mkEnableOption ''
|
||||||
the Nomarchy Plymouth boot splash (logo + progress + LUKS prompt),
|
the Nomarchy Plymouth boot splash (logo + progress + LUKS prompt),
|
||||||
background-tinted from theme-state.json via nomarchy.system.stateFile.
|
background-tinted from state.json via nomarchy.system.stateFile.
|
||||||
Recolors on system rebuilds — theme switches don't touch the initrd'' // { default = true; };
|
Recolors on system rebuilds — theme switches don't touch the initrd'' // { default = true; };
|
||||||
|
|
||||||
stateFile = lib.mkOption {
|
stateFile = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.path;
|
type = lib.types.nullOr lib.types.path;
|
||||||
default = null;
|
default = null;
|
||||||
example = lib.literalExpression "./theme-state.json";
|
example = lib.literalExpression "./state.json";
|
||||||
description = ''
|
description = ''
|
||||||
theme-state.json for the system-side consumers (currently the
|
state.json for the system-side consumers (currently the
|
||||||
Plymouth splash background). lib.mkFlake wires it automatically
|
Plymouth splash background). lib.mkFlake wires it automatically
|
||||||
from your flake; null falls back to the Boreal base color.
|
from your flake; null falls back to the Boreal base color.
|
||||||
'';
|
'';
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
bluetooth.enable = lib.mkEnableOption "Bluetooth support with blueman" // {
|
bluetooth.enable = lib.mkEnableOption "Bluetooth support with blueman" // {
|
||||||
default = true;
|
default = true;
|
||||||
defaultText = lib.literalExpression
|
defaultText = lib.literalExpression
|
||||||
"(settings.bluetooth.enable from theme-state.json) or true";
|
"(settings.bluetooth.enable from state.json) or true";
|
||||||
};
|
};
|
||||||
|
|
||||||
autoTimezone.enable = lib.mkEnableOption ''
|
autoTimezone.enable = lib.mkEnableOption ''
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
batteryChargeLimit = lib.mkOption {
|
batteryChargeLimit = lib.mkOption {
|
||||||
type = lib.types.nullOr (lib.types.ints.between 50 100);
|
type = lib.types.nullOr (lib.types.ints.between 50 100);
|
||||||
# No state bridge at eval time, by design: ./power.nix's oneshot reads
|
# No state bridge at eval time, by design: ./power.nix's oneshot reads
|
||||||
# settings.power.batteryChargeLimit out of the live theme-state.json
|
# settings.power.batteryChargeLimit out of the live state.json
|
||||||
# with jq at *runtime* and prefers it over this baked value, so the
|
# with jq at *runtime* and prefers it over this baked value, so the
|
||||||
# menu applies before (and without) a rebuild. This used to read
|
# menu applies before (and without) a rebuild. This used to read
|
||||||
# `config.nomarchy.settings…`, which does not exist on the NixOS side
|
# `config.nomarchy.settings…`, which does not exist on the NixOS side
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Plymouth boot splash — Nomarchy-branded (ported from the legacy
|
# Plymouth boot splash — Nomarchy-branded (ported from the legacy
|
||||||
# iteration), background tinted from the same theme-state.json that
|
# iteration), background tinted from the same state.json that
|
||||||
# drives the desktop (nomarchy.system.stateFile, wired automatically by
|
# drives the desktop (nomarchy.system.stateFile, wired automatically by
|
||||||
# lib.mkFlake). One caveat by design: theme switches are Home
|
# lib.mkFlake). One caveat by design: theme switches are Home
|
||||||
# Manager-only and never touch the initrd, so the splash follows the
|
# Manager-only and never touch the initrd, so the splash follows the
|
||||||
# theme as of the last SYSTEM rebuild (`sys-update`), not the last
|
# theme as of the last SYSTEM rebuild (`sys-update`), not the last
|
||||||
# `nomarchy-theme-sync apply`.
|
# `nomarchy-state-sync apply`.
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
@@ -12,7 +12,7 @@ let
|
|||||||
|
|
||||||
state =
|
state =
|
||||||
if cfg.stateFile != null
|
if cfg.stateFile != null
|
||||||
then import ../theme-state-read.nix { inherit lib; } cfg.stateFile
|
then import ../state-read.nix { inherit lib; } cfg.stateFile
|
||||||
else { };
|
else { };
|
||||||
colorOf = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback);
|
colorOf = key: fallback: lib.removePrefix "#" ((state.colors or { }).${key} or fallback);
|
||||||
# Fallbacks match the distro default theme (Boreal) when stateFile is null.
|
# Fallbacks match the distro default theme (Boreal) when stateFile is null.
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ in
|
|||||||
# Instant menu path (user decision 2026-07-10): the threshold node is
|
# Instant menu path (user decision 2026-07-10): the threshold node is
|
||||||
# group-writable for `users` so nomarchy-menu can echo live without
|
# group-writable for `users` so nomarchy-menu can echo live without
|
||||||
# rebuild; this oneshot still owns boot + AC-replug re-apply. Prefer
|
# rebuild; this oneshot still owns boot + AC-replug re-apply. Prefer
|
||||||
# live theme-state.json under /home/*/.nomarchy so a menu change
|
# live state.json under /home/*/.nomarchy so a menu change
|
||||||
# survives reboot before the next sys-rebuild bakes the Nix option.
|
# survives reboot before the next sys-rebuild bakes the Nix option.
|
||||||
systemd.services.nomarchy-battery-charge-limit = lib.mkIf cfg.laptop {
|
systemd.services.nomarchy-battery-charge-limit = lib.mkIf cfg.laptop {
|
||||||
description = "Apply battery charge end threshold from state or config";
|
description = "Apply battery charge end threshold from state or config";
|
||||||
@@ -80,7 +80,7 @@ in
|
|||||||
# Baked generation default (empty = full charge / no cap).
|
# Baked generation default (empty = full charge / no cap).
|
||||||
limit=${if cfg.batteryChargeLimit != null then toString cfg.batteryChargeLimit else ""}
|
limit=${if cfg.batteryChargeLimit != null then toString cfg.batteryChargeLimit else ""}
|
||||||
# Prefer the newest live state write (menu path, no rebuild yet).
|
# Prefer the newest live state write (menu path, no rebuild yet).
|
||||||
for st in /home/*/.nomarchy/theme-state.json; do
|
for st in /home/*/.nomarchy/state.json; do
|
||||||
[ -r "$st" ] || continue
|
[ -r "$st" ] || continue
|
||||||
v=$(jq -r '.settings.power.batteryChargeLimit // empty' "$st" 2>/dev/null || true)
|
v=$(jq -r '.settings.power.batteryChargeLimit // empty' "$st" 2>/dev/null || true)
|
||||||
case "$v" in
|
case "$v" in
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ let
|
|||||||
# Control Center's Printing toggle writes settings.printing.enable; read it
|
# Control Center's Printing toggle writes settings.printing.enable; read it
|
||||||
# from the state file, the only place it exists on the NixOS side (the
|
# from the state file, the only place it exists on the NixOS side (the
|
||||||
# hardware.nix/timezone.nix bridge). Missing/invalid JSON fails closed via
|
# hardware.nix/timezone.nix bridge). Missing/invalid JSON fails closed via
|
||||||
# theme-state-read.nix rather than a raw stack. null = key absent, which
|
# state-read.nix rather than a raw stack. null = key absent, which
|
||||||
# leaves the option's own default alone.
|
# leaves the option's own default alone.
|
||||||
svcState =
|
svcState =
|
||||||
if config.nomarchy.system.stateFile != null
|
if config.nomarchy.system.stateFile != null
|
||||||
then import ../theme-state-read.nix { inherit lib; } config.nomarchy.system.stateFile
|
then import ../state-read.nix { inherit lib; } config.nomarchy.system.stateFile
|
||||||
else { };
|
else { };
|
||||||
statePrinting =
|
statePrinting =
|
||||||
let v = (svcState.settings or { }).printing.enable or null;
|
let v = (svcState.settings or { }).printing.enable or null;
|
||||||
@@ -97,7 +97,7 @@ in
|
|||||||
System ▸ Printers entry opens the system-config-printer GUI'' // {
|
System ▸ Printers entry opens the system-config-printer GUI'' // {
|
||||||
default = false;
|
default = false;
|
||||||
defaultText = lib.literalExpression
|
defaultText = lib.literalExpression
|
||||||
"(settings.printing.enable from theme-state.json) or false";
|
"(settings.printing.enable from state.json) or false";
|
||||||
};
|
};
|
||||||
|
|
||||||
openrgb.enable = lib.mkEnableOption ''
|
openrgb.enable = lib.mkEnableOption ''
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# /etc/localtime at runtime.
|
# /etc/localtime at runtime.
|
||||||
#
|
#
|
||||||
# In-flake state, menu-driven (the keyboard/night-light philosophy): the on/off
|
# In-flake state, menu-driven (the keyboard/night-light philosophy): the on/off
|
||||||
# flag lives in the same theme-state.json under `settings.autoTimezone`
|
# flag lives in the same state.json under `settings.autoTimezone`
|
||||||
# (git-tracked, reproducible), written by the System-menu toggle
|
# (git-tracked, reproducible), written by the System-menu toggle
|
||||||
# (nomarchy-autotimezone). Because this is a SYSTEM service — not a user unit it
|
# (nomarchy-autotimezone). Because this is a SYSTEM service — not a user unit it
|
||||||
# can start/stop instantly like night-light — the toggle drives a system rebuild
|
# can start/stop instantly like night-light — the toggle drives a system rebuild
|
||||||
@@ -16,14 +16,14 @@ let
|
|||||||
|
|
||||||
# Read the same state file the rest of the system side uses (Plymouth too),
|
# Read the same state file the rest of the system side uses (Plymouth too),
|
||||||
# wired by lib.mkFlake. The flag defaults off when stateFile is null;
|
# wired by lib.mkFlake. The flag defaults off when stateFile is null;
|
||||||
# a set-but-missing/invalid path fails closed via theme-state-read.nix.
|
# a set-but-missing/invalid path fails closed via state-read.nix.
|
||||||
state =
|
state =
|
||||||
if cfg.stateFile != null
|
if cfg.stateFile != null
|
||||||
then import ../theme-state-read.nix { inherit lib; } cfg.stateFile
|
then import ../state-read.nix { inherit lib; } cfg.stateFile
|
||||||
else { };
|
else { };
|
||||||
stateEnabled = (state.settings or { }).autoTimezone or false;
|
stateEnabled = (state.settings or { }).autoTimezone or false;
|
||||||
|
|
||||||
sync = lib.getExe pkgs.nomarchy-theme-sync;
|
sync = lib.getExe pkgs.nomarchy-state-sync;
|
||||||
|
|
||||||
# Menu/CLI toggle. Runs as the normal user (it owns the flake checkout +
|
# Menu/CLI toggle. Runs as the normal user (it owns the flake checkout +
|
||||||
# writes the state); sudos only the system switch, like sys-update. Writes
|
# writes the state); sudos only the system switch, like sys-update. Writes
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Pure theme-state.json loader — fail closed with a short, actionable
|
# Pure state.json loader — fail closed with a short, actionable
|
||||||
# message instead of a raw `readFile` / `fromJSON` stack buried in a
|
# message instead of a raw `readFile` / `fromJSON` stack buried in a
|
||||||
# consumer. Used by modules/home/theme.nix (required path), the NixOS
|
# consumer. Used by modules/home/theme.nix (required path), the NixOS
|
||||||
# stateFile consumers, and lib.mkFlake (early gate).
|
# stateFile consumers, and lib.mkFlake (early gate).
|
||||||
@@ -6,43 +6,49 @@
|
|||||||
# Field-level schema checks stay in theme.nix (post-defaults). This file
|
# Field-level schema checks stay in theme.nix (post-defaults). This file
|
||||||
# only gates *existence* and *JSON-shape* so the first failure the user
|
# only gates *existence* and *JSON-shape* so the first failure the user
|
||||||
# sees points at the state file, not at nightlight.nix.
|
# sees points at the state file, not at nightlight.nix.
|
||||||
|
#
|
||||||
|
# Callers may still pass a legacy path (state.json); messages name
|
||||||
|
# whatever path they gave so a half-migrated checkout is still debuggable.
|
||||||
{ lib }:
|
{ lib }:
|
||||||
|
|
||||||
path:
|
path:
|
||||||
|
|
||||||
let
|
let
|
||||||
pathStr = toString path;
|
pathStr = toString path;
|
||||||
|
baseName = baseNameOf pathStr;
|
||||||
|
|
||||||
tip = ''
|
tip = ''
|
||||||
Fix:
|
Fix:
|
||||||
• Missing file → copy the template next to your flake.nix:
|
• Missing file → copy the template next to your flake.nix:
|
||||||
templates/downstream/theme-state.json
|
templates/downstream/state.json
|
||||||
or regenerate from a preset:
|
or regenerate from a preset:
|
||||||
nomarchy-theme-sync apply boreal
|
nomarchy-state-sync apply boreal
|
||||||
• Bad syntax / wrong shape → edit theme-state.json (trailing commas
|
• Bad syntax / wrong shape → edit ${baseName} (trailing commas
|
||||||
are the usual culprit) or reset with `apply` as above.
|
are the usual culprit) or reset with `apply` as above.
|
||||||
• Field-level schema (colors, ui, border, …):
|
• Field-level schema (colors, ui, border, …):
|
||||||
nomarchy-theme-sync validate
|
nomarchy-state-sync validate
|
||||||
Eval-time field checks live in modules/home/theme.nix.'';
|
Eval-time field checks live in modules/home/theme.nix.
|
||||||
|
• Still on theme-state.json? Rename it to state.json (or just run
|
||||||
|
any menu write / `nomarchy-state-sync set` — it migrates).'';
|
||||||
|
|
||||||
missingMsg = ''
|
missingMsg = ''
|
||||||
|
|
||||||
Nomarchy: theme-state.json is missing:
|
Nomarchy: state file is missing:
|
||||||
${pathStr}
|
${pathStr}
|
||||||
|
|
||||||
This file is required (appearance + menu settings). Add it to your
|
This file is required (appearance + menu settings). Add state.json to
|
||||||
flake checkout so evaluation stays pure.
|
your flake checkout so evaluation stays pure.
|
||||||
${tip}'';
|
${tip}'';
|
||||||
|
|
||||||
emptyMsg = ''
|
emptyMsg = ''
|
||||||
|
|
||||||
Nomarchy: theme-state.json is empty:
|
Nomarchy: state file is empty:
|
||||||
${pathStr}
|
${pathStr}
|
||||||
${tip}'';
|
${tip}'';
|
||||||
|
|
||||||
notObjectMsg = ''
|
notObjectMsg = ''
|
||||||
|
|
||||||
Nomarchy: theme-state.json must be a JSON object `{ ... }`:
|
Nomarchy: state file must be a JSON object `{ ... }`:
|
||||||
${pathStr}
|
${pathStr}
|
||||||
${tip}'';
|
${tip}'';
|
||||||
|
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Nomarchy Control Center — TUI frontend over nomarchy-theme-sync and tools
|
# Nomarchy Control Center — TUI frontend over nomarchy-state-sync and tools
|
||||||
# Uses charmbracelet/gum for rendering.
|
# Uses charmbracelet/gum for rendering.
|
||||||
|
|
||||||
trap 'exit 0' SIGINT
|
trap 'exit 0' SIGINT
|
||||||
|
|
||||||
function get_state() {
|
function get_state() {
|
||||||
nomarchy-theme-sync get "$1" 2>/dev/null || echo ""
|
nomarchy-state-sync get "$1" 2>/dev/null || echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_state() {
|
function set_state() {
|
||||||
nomarchy-theme-sync --quiet set "$1" "$2" --no-switch
|
nomarchy-state-sync --quiet set "$1" "$2" --no-switch
|
||||||
}
|
}
|
||||||
|
|
||||||
function first_boot() {
|
function first_boot() {
|
||||||
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "Welcome to Nomarchy!"
|
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "Welcome to Nomarchy!"
|
||||||
|
|
||||||
echo "Pick a Theme Preset:"
|
echo "Pick a Theme Preset:"
|
||||||
themes=$(nomarchy-theme-sync list)
|
themes=$(nomarchy-state-sync list)
|
||||||
chosen_theme=$(printf "%s" "$themes" | gum choose)
|
chosen_theme=$(printf "%s" "$themes" | gum choose)
|
||||||
if [ -n "$chosen_theme" ]; then
|
if [ -n "$chosen_theme" ]; then
|
||||||
echo "Applying theme $chosen_theme..."
|
echo "Applying theme $chosen_theme..."
|
||||||
nomarchy-theme-sync apply "$chosen_theme"
|
nomarchy-state-sync apply "$chosen_theme"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if gum confirm "Enable auto-commit for your config? (settings changes + a sweep of pending edits before each rebuild)"; then
|
if gum confirm "Enable auto-commit for your config? (settings changes + a sweep of pending edits before each rebuild)"; then
|
||||||
@@ -113,10 +113,10 @@ function appearance_menu() {
|
|||||||
choice=$(gum choose "Pick Theme" "Toggle Blur" "Set Gaps" "Back")
|
choice=$(gum choose "Pick Theme" "Toggle Blur" "Set Gaps" "Back")
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
"Pick Theme")
|
"Pick Theme")
|
||||||
themes=$(nomarchy-theme-sync list)
|
themes=$(nomarchy-state-sync list)
|
||||||
chosen=$(printf "%s" "$themes" | gum choose)
|
chosen=$(printf "%s" "$themes" | gum choose)
|
||||||
if [ -n "$chosen" ]; then
|
if [ -n "$chosen" ]; then
|
||||||
nomarchy-theme-sync apply "$chosen"
|
nomarchy-state-sync apply "$chosen"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
"Toggle Blur")
|
"Toggle Blur")
|
||||||
|
|||||||
@@ -67,21 +67,33 @@ for mp in / /boot /nix; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# ── the flake + state file ───────────────────────────────────────────
|
# ── the flake + state file ───────────────────────────────────────────
|
||||||
if [ -f "$flake/theme-state.json" ]; then
|
# #107: prefer state.json; theme-state.json still counts until migrated.
|
||||||
if jq empty "$flake/theme-state.json" 2>/dev/null; then
|
state_file=
|
||||||
ok "theme-state.json parses"
|
if [ -f "$flake/state.json" ]; then
|
||||||
else
|
state_file="$flake/state.json"
|
||||||
bad "theme-state.json is not valid JSON (rebuilds will fail)" \
|
elif [ -f "$flake/theme-state.json" ]; then
|
||||||
"nomarchy-theme-sync validate (names the spot; fix it, or re-apply a theme)"
|
state_file="$flake/theme-state.json"
|
||||||
fi
|
fi
|
||||||
if git -C "$flake" ls-files --error-unmatch theme-state.json >/dev/null 2>&1; then
|
if [ -n "$state_file" ]; then
|
||||||
ok "theme-state.json is git-tracked"
|
state_base=$(basename "$state_file")
|
||||||
|
if jq empty "$state_file" 2>/dev/null; then
|
||||||
|
ok "$state_base parses"
|
||||||
else
|
else
|
||||||
bad "theme-state.json is NOT git-tracked (flake evaluation can't see it)" \
|
bad "$state_base is not valid JSON (rebuilds will fail)" \
|
||||||
"git -C $flake add theme-state.json"
|
"nomarchy-state-sync validate (names the spot; fix it, or re-apply a theme)"
|
||||||
|
fi
|
||||||
|
if git -C "$flake" ls-files --error-unmatch "$state_base" >/dev/null 2>&1; then
|
||||||
|
ok "$state_base is git-tracked"
|
||||||
|
else
|
||||||
|
bad "$state_base is NOT git-tracked (flake evaluation can't see it)" \
|
||||||
|
"git -C $flake add $state_base"
|
||||||
|
fi
|
||||||
|
if [ "$state_base" = "theme-state.json" ]; then
|
||||||
|
warn "state file still named theme-state.json — run any menu write or" \
|
||||||
|
"nomarchy-state-sync set … to migrate to state.json"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
skip "theme-state.json (no flake checkout at $flake)"
|
skip "state.json (no flake checkout at $flake)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -d "$flake/.git" ]; then
|
if [ -d "$flake/.git" ]; then
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# First-session "you're set" toast (nomarchy.firstBootWelcome). A package
|
# First-session "you're set" toast (nomarchy.firstBootWelcome). A package
|
||||||
# so checks.first-boot can exercise the gate on a minimal node — same
|
# so checks.first-boot can exercise the gate on a minimal node — same
|
||||||
# pattern as nomarchy-battery-notify. libnotify + theme-sync stay on PATH
|
# pattern as nomarchy-battery-notify. libnotify + state-sync stay on PATH
|
||||||
# (user unit / VM shim), not runtimeInputs.
|
# (user unit / VM shim), not runtimeInputs.
|
||||||
{ writeShellApplication, coreutils }:
|
{ writeShellApplication, coreutils }:
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# nomarchy-first-boot — one-shot "you're set" toast on the first session.
|
# nomarchy-first-boot — one-shot "you're set" toast on the first session.
|
||||||
# Marker is settings.firstBootShown in the flake's theme-state.json
|
# Marker is settings.firstBootShown in the flake's state.json
|
||||||
# (in-checkout state; never ~/.local/state). notify-send and
|
# (in-checkout state; never ~/.local/state). notify-send and
|
||||||
# nomarchy-theme-sync come from PATH so the VM check can shim them.
|
# nomarchy-state-sync come from PATH so the VM check can shim them.
|
||||||
|
|
||||||
# Live ISO already has its own welcome (hosts/live.nix); skip there so
|
# Live ISO already has its own welcome (hosts/live.nix); skip there so
|
||||||
# users aren't double-toasted and the live seed doesn't get a sticky
|
# users aren't double-toasted and the live seed doesn't get a sticky
|
||||||
@@ -13,8 +13,8 @@ if [ "$hn" = nomarchy-live ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Already shown → silent. Missing key / no checkout → treat as not shown
|
# Already shown → silent. Missing key / no checkout → treat as not shown
|
||||||
# (theme-sync get exits non-zero when the key is absent).
|
# (state-sync get exits non-zero when the key is absent).
|
||||||
shown=$(nomarchy-theme-sync get settings.firstBootShown 2>/dev/null || true)
|
shown=$(nomarchy-state-sync get settings.firstBootShown 2>/dev/null || true)
|
||||||
case "$shown" in
|
case "$shown" in
|
||||||
true|1|yes) exit 0 ;;
|
true|1|yes) exit 0 ;;
|
||||||
esac
|
esac
|
||||||
@@ -46,7 +46,7 @@ if [ -z "$ok" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Persist in the checkout so re-login is silent. --no-switch: marker only.
|
# Persist in the checkout so re-login is silent. --no-switch: marker only.
|
||||||
if ! nomarchy-theme-sync --quiet set settings.firstBootShown true --no-switch; then
|
if ! nomarchy-state-sync --quiet set settings.firstBootShown true --no-switch; then
|
||||||
# No writable flake checkout (or tool missing) — still showed the toast;
|
# No writable flake checkout (or tool missing) — still showed the toast;
|
||||||
# without a marker it may reappear next login. Don't fail the unit.
|
# without a marker it may reappear next login. Don't fail the unit.
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
, btrfs-progs # inspect-internal map-swapfile (hibernation offset)
|
, btrfs-progs # inspect-internal map-swapfile (hibernation offset)
|
||||||
, xkeyboard_config # human-readable installed layout/variant catalog
|
, xkeyboard_config # human-readable installed layout/variant catalog
|
||||||
# Baked metadata — what this installer installs and where it came from.
|
# Baked metadata — what this installer installs and where it came from.
|
||||||
, templateDir # templates/downstream (home.nix, theme-state.json)
|
, templateDir # templates/downstream (home.nix, state.json)
|
||||||
, nomarchyLock # the distro's flake.lock (for offline lock composition)
|
, nomarchyLock # the distro's flake.lock (for offline lock composition)
|
||||||
, hardwareModuleNames # newline-separated nixos-hardware module names
|
, hardwareModuleNames # newline-separated nixos-hardware module names
|
||||||
, nixpkgsPath # pinned nixpkgs source (NIX_PATH for disko's eval)
|
, nixpkgsPath # pinned nixpkgs source (NIX_PATH for disko's eval)
|
||||||
@@ -54,7 +54,7 @@ stdenvNoCC.mkDerivation {
|
|||||||
mkdir -p "$share/template"
|
mkdir -p "$share/template"
|
||||||
# Full downstream template is the SoT; install script copies + patches.
|
# Full downstream template is the SoT; install script copies + patches.
|
||||||
cp ${templateDir}/flake.nix ${templateDir}/system.nix \
|
cp ${templateDir}/flake.nix ${templateDir}/system.nix \
|
||||||
${templateDir}/home.nix ${templateDir}/theme-state.json \
|
${templateDir}/home.nix ${templateDir}/state.json \
|
||||||
"$share/template/"
|
"$share/template/"
|
||||||
install -Dm644 patch-template.py "$share/patch-template.py"
|
install -Dm644 patch-template.py "$share/patch-template.py"
|
||||||
|
|
||||||
|
|||||||
@@ -585,7 +585,7 @@ rm -rf /mnt/etc/nixos
|
|||||||
cp "$SHARE/template/flake.nix" \
|
cp "$SHARE/template/flake.nix" \
|
||||||
"$SHARE/template/system.nix" \
|
"$SHARE/template/system.nix" \
|
||||||
"$SHARE/template/home.nix" \
|
"$SHARE/template/home.nix" \
|
||||||
"$SHARE/template/theme-state.json" \
|
"$SHARE/template/state.json" \
|
||||||
"$FLAKE_DIR/"
|
"$FLAKE_DIR/"
|
||||||
|
|
||||||
# Detected hardware → flags for the patcher (safe defaults active).
|
# Detected hardware → flags for the patcher (safe defaults active).
|
||||||
@@ -679,7 +679,7 @@ if (( compose_ok != 0 )); then
|
|||||||
(cd "$FLAKE_DIR" && nix --extra-experimental-features "nix-command flakes" flake lock)
|
(cd "$FLAKE_DIR" && nix --extra-experimental-features "nix-command flakes" flake lock)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# A flake worktree must be git-tracked (theme-state.json especially).
|
# A flake worktree must be git-tracked (state.json especially).
|
||||||
(
|
(
|
||||||
cd "$FLAKE_DIR"
|
cd "$FLAKE_DIR"
|
||||||
git init -q
|
git init -q
|
||||||
@@ -702,7 +702,7 @@ success "Configuration written to ~$USERNAME/.nomarchy"
|
|||||||
section "Installing (this takes a while)"
|
section "Installing (this takes a while)"
|
||||||
|
|
||||||
# Seed the target store with the flake source + all inputs so the first
|
# Seed the target store with the flake source + all inputs so the first
|
||||||
# `nomarchy-theme-sync apply` (and the HM pre-activation below) work
|
# `nomarchy-state-sync apply` (and the HM pre-activation below) work
|
||||||
# before the machine has ever seen a network. Two steps because
|
# before the machine has ever seen a network. Two steps because
|
||||||
# `flake archive --to` enforces signatures and locally-evaluated source
|
# `flake archive --to` enforces signatures and locally-evaluated source
|
||||||
# paths have none; plain `nix copy` accepts --no-check-sigs.
|
# paths have none; plain `nix copy` accepts --no-check-sigs.
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ def build_installer_region(v: dict) -> str:
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Auto-login is deliberately NOT emitted here — it is seeded into
|
# Auto-login is deliberately NOT emitted here — it is seeded into
|
||||||
# theme-state.json instead (patch_state). A line in system.nix outranks
|
# state.json instead (patch_state). A line in system.nix outranks
|
||||||
# the state default, which would make the System › Auto-login toggle
|
# the state default, which would make the System › Auto-login toggle
|
||||||
# write JSON that nothing reads.
|
# write JSON that nothing reads.
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ def patch_system(text: str, v: dict) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def patch_state(text: str, v: dict) -> str:
|
def patch_state(text: str, v: dict) -> str:
|
||||||
"""Seed menu-owned settings into theme-state.json.
|
"""Seed menu-owned settings into state.json.
|
||||||
|
|
||||||
These live in the state rather than system.nix precisely so the menu can
|
These live in the state rather than system.nix precisely so the menu can
|
||||||
change them later: a baked Nix assignment would outrank the state default
|
change them later: a baked Nix assignment would outrank the state default
|
||||||
@@ -286,7 +286,7 @@ def main() -> None:
|
|||||||
"flake.nix": patch_flake,
|
"flake.nix": patch_flake,
|
||||||
"home.nix": patch_home,
|
"home.nix": patch_home,
|
||||||
"system.nix": patch_system,
|
"system.nix": patch_system,
|
||||||
"theme-state.json": patch_state,
|
"state.json": patch_state,
|
||||||
}
|
}
|
||||||
for name, fn in mapping.items():
|
for name, fn in mapping.items():
|
||||||
path = flake_dir / name
|
path = flake_dir / name
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ let
|
|||||||
flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}"
|
flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Opt-in sweep (settings.autoCommit — the same flag theme-sync honours):
|
# Opt-in sweep (settings.autoCommit — the same flag state-sync honours):
|
||||||
# commit EVERYTHING dirty in the machine flake before a pull/rebuild/
|
# commit EVERYTHING dirty in the machine flake before a pull/rebuild/
|
||||||
# home switch, so hand edits to system.nix/home.nix and lock bumps land
|
# home switch, so hand edits to system.nix/home.nix and lock bumps land
|
||||||
# in history at the moment they become live — `git log` mirrors the
|
# in history at the moment they become live — `git log` mirrors the
|
||||||
# generation list. Complements nomarchy-theme-sync's auto_commit, which
|
# generation list. Complements nomarchy-state-sync's auto_commit, which
|
||||||
# is pathspec-limited to theme-state.json on menu writes precisely so
|
# is pathspec-limited to state.json on menu writes precisely so
|
||||||
# half-finished hand edits never ride a settings-named commit; here the
|
# half-finished hand edits never ride a settings-named commit; here the
|
||||||
# sweep is the point, and the commit body lists what was swept. Never
|
# sweep is the point, and the commit body lists what was swept. Never
|
||||||
# fatal — callers `|| true` so a git hiccup can't block a rebuild. Not
|
# fatal — callers `|| true` so a git hiccup can't block a rebuild. Not
|
||||||
@@ -31,8 +31,12 @@ let
|
|||||||
label="''${1:-rebuild}"
|
label="''${1:-rebuild}"
|
||||||
[ -d "$flake/.git" ] || exit 0
|
[ -d "$flake/.git" ] || exit 0
|
||||||
command -v git >/dev/null 2>&1 || exit 0
|
command -v git >/dev/null 2>&1 || exit 0
|
||||||
|
# #107: prefer state.json; still honour a legacy theme-state.json
|
||||||
|
# until the next menu write migrates the checkout.
|
||||||
|
state="$flake/state.json"
|
||||||
|
[ -r "$state" ] || state="$flake/theme-state.json"
|
||||||
flag=$(${jq}/bin/jq -r '.settings.autoCommit // false' \
|
flag=$(${jq}/bin/jq -r '.settings.autoCommit // false' \
|
||||||
"$flake/theme-state.json" 2>/dev/null || true)
|
"$state" 2>/dev/null || true)
|
||||||
[ "$flag" = "true" ] || exit 0
|
[ "$flag" = "true" ] || exit 0
|
||||||
dirty=$(git -C "$flake" status --porcelain || true)
|
dirty=$(git -C "$flake" status --porcelain || true)
|
||||||
[ -n "$dirty" ] || exit 0
|
[ -n "$dirty" ] || exit 0
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
}:
|
}:
|
||||||
|
|
||||||
stdenvNoCC.mkDerivation {
|
stdenvNoCC.mkDerivation {
|
||||||
pname = "nomarchy-theme-sync";
|
pname = "nomarchy-state-sync";
|
||||||
version = "0.4.0";
|
version = "0.5.0";
|
||||||
|
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|
||||||
@@ -22,8 +22,8 @@ stdenvNoCC.mkDerivation {
|
|||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
runHook preInstall
|
||||||
|
|
||||||
install -Dm755 nomarchy-theme-sync.py $out/bin/nomarchy-theme-sync
|
install -Dm755 nomarchy-state-sync.py $out/bin/nomarchy-state-sync
|
||||||
patchShebangs $out/bin/nomarchy-theme-sync
|
patchShebangs $out/bin/nomarchy-state-sync
|
||||||
|
|
||||||
${lib.optionalString (themesDir != null) ''
|
${lib.optionalString (themesDir != null) ''
|
||||||
mkdir -p $out/share/nomarchy
|
mkdir -p $out/share/nomarchy
|
||||||
@@ -32,18 +32,23 @@ stdenvNoCC.mkDerivation {
|
|||||||
|
|
||||||
# Stdlib-only Python. home-manager is deliberately NOT wrapped in —
|
# Stdlib-only Python. home-manager is deliberately NOT wrapped in —
|
||||||
# the rebuild must use the user's own home-manager from their PATH.
|
# the rebuild must use the user's own home-manager from their PATH.
|
||||||
wrapProgram $out/bin/nomarchy-theme-sync \
|
wrapProgram $out/bin/nomarchy-state-sync \
|
||||||
--prefix PATH : ${lib.makeBinPath [ awww libnotify git ]} \
|
--prefix PATH : ${lib.makeBinPath [ awww libnotify git ]} \
|
||||||
${lib.optionalString (themesDir != null)
|
${lib.optionalString (themesDir != null)
|
||||||
"--set NOMARCHY_DEFAULT_THEMES $out/share/nomarchy/themes"}
|
"--set NOMARCHY_DEFAULT_THEMES $out/share/nomarchy/themes"}
|
||||||
|
|
||||||
|
# #107: old CLI name kept as a symlink so muscle memory and scripts
|
||||||
|
# survive a pull (drop after the next stable release notes say so).
|
||||||
|
# After wrapProgram so it points at the wrapper, not the raw script.
|
||||||
|
ln -s nomarchy-state-sync $out/bin/nomarchy-theme-sync
|
||||||
|
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
description = "Nomarchy theming: JSON state writer + Home Manager rebuild dispatcher";
|
description = "Nomarchy state writer + Home Manager rebuild dispatcher";
|
||||||
license = lib.licenses.mit;
|
license = lib.licenses.mit;
|
||||||
mainProgram = "nomarchy-theme-sync";
|
mainProgram = "nomarchy-state-sync";
|
||||||
platforms = lib.platforms.linux;
|
platforms = lib.platforms.linux;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""nomarchy-theme-sync — state writer for Nomarchy's declarative theming.
|
"""nomarchy-state-sync — state writer for Nomarchy's declarative theming.
|
||||||
|
|
||||||
Single source of truth: $NOMARCHY_PATH/theme-state.json (inside the flake,
|
Single source of truth: $NOMARCHY_PATH/state.json (inside the flake,
|
||||||
read purely by Home Manager via the nomarchy.stateFile option).
|
read purely by Home Manager via the nomarchy.stateFile option).
|
||||||
|
Legacy name `theme-state.json` is still read (and migrated on write).
|
||||||
|
The old binary name `nomarchy-theme-sync` is a symlink to this tool.
|
||||||
|
|
||||||
Theme changes are applied by Home Manager: this tool writes the new state
|
Theme changes are applied by Home Manager: this tool writes the new state
|
||||||
and runs `home-manager switch` (override with $NOMARCHY_REBUILD, or skip
|
and runs `home-manager switch` (override with $NOMARCHY_REBUILD, or skip
|
||||||
@@ -39,7 +41,24 @@ from pathlib import Path
|
|||||||
# ─── Paths ────────────────────────────────────────────────────────────────
|
# ─── Paths ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
FLAKE_DIR = Path(os.environ.get("NOMARCHY_PATH", Path.home() / ".nomarchy")).expanduser()
|
FLAKE_DIR = Path(os.environ.get("NOMARCHY_PATH", Path.home() / ".nomarchy")).expanduser()
|
||||||
STATE_FILE = FLAKE_DIR / "theme-state.json"
|
# Canonical name after #107. Legacy theme-state.json (and a brief theme.json
|
||||||
|
# misnomer some notes used) are still accepted for existing checkouts.
|
||||||
|
STATE_NAME = "state.json"
|
||||||
|
LEGACY_STATE_NAMES = ("theme-state.json", "theme.json")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_state_file(flake_dir: Path = FLAKE_DIR) -> Path:
|
||||||
|
preferred = flake_dir / STATE_NAME
|
||||||
|
if preferred.exists():
|
||||||
|
return preferred
|
||||||
|
for name in LEGACY_STATE_NAMES:
|
||||||
|
legacy = flake_dir / name
|
||||||
|
if legacy.exists():
|
||||||
|
return legacy
|
||||||
|
return preferred
|
||||||
|
|
||||||
|
|
||||||
|
STATE_FILE = resolve_state_file()
|
||||||
|
|
||||||
# Preset search path: the user's flake first (their custom themes win),
|
# Preset search path: the user's flake first (their custom themes win),
|
||||||
# then the presets baked into this package by the Nix build.
|
# then the presets baked into this package by the Nix build.
|
||||||
@@ -51,21 +70,24 @@ WALLPAPER_EXTS = {".png", ".jpg", ".jpeg", ".webp"}
|
|||||||
|
|
||||||
QUIET = False
|
QUIET = False
|
||||||
|
|
||||||
|
# argv[0] may still be the compatibility name nomarchy-theme-sync.
|
||||||
|
_TOOL = Path(sys.argv[0]).name or "nomarchy-state-sync"
|
||||||
|
|
||||||
|
|
||||||
def log(msg: str) -> None:
|
def log(msg: str) -> None:
|
||||||
if not QUIET:
|
if not QUIET:
|
||||||
print(f"nomarchy-theme-sync: {msg}")
|
print(f"{_TOOL}: {msg}")
|
||||||
|
|
||||||
|
|
||||||
def die(msg: str) -> "None":
|
def die(msg: str) -> "None":
|
||||||
print(f"nomarchy-theme-sync: error: {msg}", file=sys.stderr)
|
print(f"{_TOOL}: error: {msg}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
# All Nomarchy theme notifications share this synchronous tag, so the
|
# All Nomarchy theme notifications share this synchronous tag, so the
|
||||||
# notification daemon (swaync) REPLACES the previous one in place instead
|
# notification daemon (swaync) REPLACES the previous one in place instead
|
||||||
# of stacking — the "rebuilding…" toast becomes the "applied ✓" toast.
|
# of stacking — the "rebuilding…" toast becomes the "applied ✓" toast.
|
||||||
NOTIFY_SYNC_TAG = "nomarchy-theme-sync"
|
NOTIFY_SYNC_TAG = "nomarchy-state-sync"
|
||||||
|
|
||||||
|
|
||||||
def notify(body: str, summary: str = "Nomarchy", *,
|
def notify(body: str, summary: str = "Nomarchy", *,
|
||||||
@@ -87,13 +109,14 @@ def notify(body: str, summary: str = "Nomarchy", *,
|
|||||||
|
|
||||||
# ─── State management ─────────────────────────────────────────────────────
|
# ─── State management ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
def load_state(path: Path = STATE_FILE) -> dict:
|
def load_state(path: Path | None = None) -> dict:
|
||||||
|
path = path or resolve_state_file()
|
||||||
try:
|
try:
|
||||||
return json.loads(path.read_text())
|
return json.loads(path.read_text())
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
die(f"state file not found: {path} (set $NOMARCHY_PATH to your flake checkout)")
|
die(f"state file not found: {path} (set $NOMARCHY_PATH to your flake checkout)")
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
die(f"theme-state.json has a JSON syntax error at line {e.lineno}, "
|
die(f"{path.name} has a JSON syntax error at line {e.lineno}, "
|
||||||
f"column {e.colno}: {e.msg}\n"
|
f"column {e.colno}: {e.msg}\n"
|
||||||
f" file: {path}\n"
|
f" file: {path}\n"
|
||||||
f" fix: correct the syntax by hand (a trailing comma is the "
|
f" fix: correct the syntax by hand (a trailing comma is the "
|
||||||
@@ -103,30 +126,46 @@ def load_state(path: Path = STATE_FILE) -> dict:
|
|||||||
def write_state(state: dict) -> None:
|
def write_state(state: dict) -> None:
|
||||||
"""Atomic write: render to a temp file in the same dir, then rename.
|
"""Atomic write: render to a temp file in the same dir, then rename.
|
||||||
Validates first — an invalid state never reaches disk (the old file
|
Validates first — an invalid state never reaches disk (the old file
|
||||||
stays untouched), so a bad `set` can't brick the next rebuild."""
|
stays untouched), so a bad `set` can't brick the next rebuild.
|
||||||
|
Always writes the canonical state.json; removes a legacy-named sibling
|
||||||
|
so a checkout never has two competing sources of truth."""
|
||||||
errors, warnings = validate_state(state)
|
errors, warnings = validate_state(state)
|
||||||
for w in warnings:
|
for w in warnings:
|
||||||
log(f"warning: {w}")
|
log(f"warning: {w}")
|
||||||
if errors:
|
if errors:
|
||||||
die("refusing to write an invalid state (nothing changed):\n ✖ "
|
die("refusing to write an invalid state (nothing changed):\n ✖ "
|
||||||
+ "\n ✖ ".join(errors))
|
+ "\n ✖ ".join(errors))
|
||||||
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
out = FLAKE_DIR / STATE_NAME
|
||||||
fd, tmp = tempfile.mkstemp(dir=STATE_FILE.parent, prefix=".theme-state.", suffix=".json")
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
fd, tmp = tempfile.mkstemp(dir=out.parent, prefix=".state.", suffix=".json")
|
||||||
try:
|
try:
|
||||||
with os.fdopen(fd, "w") as f:
|
with os.fdopen(fd, "w") as f:
|
||||||
json.dump(state, f, indent=2)
|
json.dump(state, f, indent=2)
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
os.replace(tmp, STATE_FILE)
|
os.replace(tmp, out)
|
||||||
except BaseException:
|
except BaseException:
|
||||||
os.unlink(tmp)
|
os.unlink(tmp)
|
||||||
raise
|
raise
|
||||||
log(f"state written to {STATE_FILE}")
|
for name in LEGACY_STATE_NAMES:
|
||||||
|
legacy = FLAKE_DIR / name
|
||||||
|
if legacy.exists() and legacy.resolve() != out.resolve():
|
||||||
|
try:
|
||||||
|
legacy.unlink()
|
||||||
|
log(f"migrated: removed legacy {name} (now {STATE_NAME})")
|
||||||
|
except OSError as e:
|
||||||
|
log(f"warning: could not remove legacy {name}: {e}")
|
||||||
|
log(f"state written to {out}")
|
||||||
|
|
||||||
# Flakes only see git-tracked files. theme-state.json ships tracked,
|
# Flakes only see git-tracked files. state.json ships tracked,
|
||||||
# but make sure a fresh checkout / reset can't silently hide it.
|
# but make sure a fresh checkout / reset can't silently hide it.
|
||||||
if (FLAKE_DIR / ".git").exists() and shutil.which("git"):
|
if (FLAKE_DIR / ".git").exists() and shutil.which("git"):
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["git", "-C", str(FLAKE_DIR), "add", "--intent-to-add", "theme-state.json"],
|
["git", "-C", str(FLAKE_DIR), "add", "--intent-to-add", STATE_NAME],
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
for name in LEGACY_STATE_NAMES:
|
||||||
|
subprocess.run(
|
||||||
|
["git", "-C", str(FLAKE_DIR), "rm", "-f", "--ignore-unmatch", name],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -136,7 +175,7 @@ def auto_commit_enabled(state: dict) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def auto_commit(message: str) -> None:
|
def auto_commit(message: str) -> None:
|
||||||
"""Opt-in (settings.autoCommit): commit theme-state.json — and nothing
|
"""Opt-in (settings.autoCommit): commit state.json — and nothing
|
||||||
else — after a mutation, so settings history is `git log`. The pathspec
|
else — after a mutation, so settings history is `git log`. The pathspec
|
||||||
keeps unrelated dirty files out of the commit; a missing git identity
|
keeps unrelated dirty files out of the commit; a missing git identity
|
||||||
falls back to a Nomarchy one so a fresh machine never errors. Callers
|
falls back to a Nomarchy one so a fresh machine never errors. Callers
|
||||||
@@ -150,16 +189,18 @@ def auto_commit(message: str) -> None:
|
|||||||
if not (FLAKE_DIR / ".git").exists() or shutil.which("git") is None:
|
if not (FLAKE_DIR / ".git").exists() or shutil.which("git") is None:
|
||||||
return
|
return
|
||||||
git = ["git", "-C", str(FLAKE_DIR)]
|
git = ["git", "-C", str(FLAKE_DIR)]
|
||||||
|
# Pathspec: preferred name plus any leftover legacy file still staged.
|
||||||
|
paths = [STATE_NAME, *LEGACY_STATE_NAMES]
|
||||||
# No-op when the file already matches HEAD (a `set` to the same value).
|
# No-op when the file already matches HEAD (a `set` to the same value).
|
||||||
# On a repo with no commits yet this diff errors — then just commit.
|
# On a repo with no commits yet this diff errors — then just commit.
|
||||||
if subprocess.run(git + ["diff", "--quiet", "HEAD", "--", "theme-state.json"],
|
if subprocess.run(git + ["diff", "--quiet", "HEAD", "--"] + paths,
|
||||||
capture_output=True).returncode == 0:
|
capture_output=True).returncode == 0:
|
||||||
return
|
return
|
||||||
if not subprocess.run(git + ["config", "user.email"],
|
if not subprocess.run(git + ["config", "user.email"],
|
||||||
capture_output=True, text=True).stdout.strip():
|
capture_output=True, text=True).stdout.strip():
|
||||||
git += ["-c", "user.name=Nomarchy", "-c", "user.email=nomarchy@localhost"]
|
git += ["-c", "user.name=Nomarchy", "-c", "user.email=nomarchy@localhost"]
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
git + ["commit", "--quiet", "-m", message, "--", "theme-state.json"],
|
git + ["commit", "--quiet", "-m", message, "--"] + paths,
|
||||||
capture_output=True, text=True)
|
capture_output=True, text=True)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
log(f"auto-committed: {message}")
|
log(f"auto-committed: {message}")
|
||||||
@@ -212,7 +253,7 @@ def validate_state(state) -> tuple:
|
|||||||
|
|
||||||
if not isinstance(state, dict):
|
if not isinstance(state, dict):
|
||||||
err("top level", "must be a JSON object",
|
err("top level", "must be a JSON object",
|
||||||
"re-apply a preset: nomarchy-theme-sync apply boreal")
|
"re-apply a preset: nomarchy-state-sync apply boreal")
|
||||||
return errors, warnings
|
return errors, warnings
|
||||||
|
|
||||||
for k in state:
|
for k in state:
|
||||||
@@ -315,7 +356,7 @@ def cmd_validate(_args) -> None:
|
|||||||
for w in warnings:
|
for w in warnings:
|
||||||
print(f" ● {w}")
|
print(f" ● {w}")
|
||||||
if errors:
|
if errors:
|
||||||
print(f"nomarchy-theme-sync: {STATE_FILE} has "
|
print(f"nomarchy-state-sync: {STATE_FILE} has "
|
||||||
f"{len(errors)} problem(s):", file=sys.stderr)
|
f"{len(errors)} problem(s):", file=sys.stderr)
|
||||||
for e in errors:
|
for e in errors:
|
||||||
print(f" ✖ {e}", file=sys.stderr)
|
print(f" ✖ {e}", file=sys.stderr)
|
||||||
@@ -340,7 +381,7 @@ def check_fonts(state: dict) -> None:
|
|||||||
families = [f.strip().lower() for f in result.stdout.split(",")]
|
families = [f.strip().lower() for f in result.stdout.split(",")]
|
||||||
if result.returncode != 0 or name.strip().lower() not in families:
|
if result.returncode != 0 or name.strip().lower() not in families:
|
||||||
fallback = result.stdout.strip() or "unknown"
|
fallback = result.stdout.strip() or "unknown"
|
||||||
print(f"nomarchy-theme-sync: warning: fonts.{key} '{name}' is not "
|
print(f"nomarchy-state-sync: warning: fonts.{key} '{name}' is not "
|
||||||
f"installed — fontconfig will substitute '{fallback}'",
|
f"installed — fontconfig will substitute '{fallback}'",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
notify(f"Font '{name}' is not installed — using '{fallback}'")
|
notify(f"Font '{name}' is not installed — using '{fallback}'")
|
||||||
@@ -490,7 +531,7 @@ def apply_named(theme: str, no_switch: bool = False) -> None:
|
|||||||
candidate = Path(theme).expanduser()
|
candidate = Path(theme).expanduser()
|
||||||
preset_path = candidate if candidate.is_file() else find_preset(theme)
|
preset_path = candidate if candidate.is_file() else find_preset(theme)
|
||||||
if preset_path is None:
|
if preset_path is None:
|
||||||
die(f"unknown theme '{theme}' (try `nomarchy-theme-sync list`)")
|
die(f"unknown theme '{theme}' (try `nomarchy-state-sync list`)")
|
||||||
|
|
||||||
preset = json.loads(preset_path.read_text())
|
preset = json.loads(preset_path.read_text())
|
||||||
# Merge over current state. Presets carry a full appearance block —
|
# Merge over current state. Presets carry a full appearance block —
|
||||||
@@ -642,7 +683,7 @@ def main() -> None:
|
|||||||
global QUIET
|
global QUIET
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog="nomarchy-theme-sync",
|
prog="nomarchy-state-sync",
|
||||||
description="Nomarchy theming — state writer + Home Manager rebuild dispatcher.",
|
description="Nomarchy theming — state writer + Home Manager rebuild dispatcher.",
|
||||||
)
|
)
|
||||||
parser.add_argument("--quiet", action="store_true", help="suppress progress output")
|
parser.add_argument("--quiet", action="store_true", help="suppress progress output")
|
||||||
@@ -671,7 +712,7 @@ def main() -> None:
|
|||||||
p.add_argument("--no-switch", action="store_true", help="write state only, skip the rebuild")
|
p.add_argument("--no-switch", action="store_true", help="write state only, skip the rebuild")
|
||||||
p.set_defaults(func=cmd_auto)
|
p.set_defaults(func=cmd_auto)
|
||||||
|
|
||||||
sub.add_parser("validate", help="check theme-state.json against the schema (read-only)").set_defaults(func=cmd_validate)
|
sub.add_parser("validate", help="check state.json against the schema (read-only)").set_defaults(func=cmd_validate)
|
||||||
|
|
||||||
sub.add_parser("wallpaper", help="apply the current wallpaper via swww").set_defaults(func=cmd_wallpaper)
|
sub.add_parser("wallpaper", help="apply the current wallpaper via swww").set_defaults(func=cmd_wallpaper)
|
||||||
|
|
||||||
@@ -15,19 +15,19 @@ keyboard, detected hardware, password hash, …). Keep commented opt-ins and
|
|||||||
services — the login user is created from `username` automatically) and
|
services — the login user is created from `username` automatically) and
|
||||||
`home.nix` (your packages).
|
`home.nix` (your packages).
|
||||||
3. `git init && git add -A` — flakes only see tracked files, including
|
3. `git init && git add -A` — flakes only see tracked files, including
|
||||||
`theme-state.json`.
|
`state.json`.
|
||||||
4. System: `sudo nixos-rebuild switch --flake .#default`
|
4. System: `sudo nixos-rebuild switch --flake .#default`
|
||||||
5. Desktop: `nix run home-manager -- switch --flake .#me`
|
5. Desktop: `nix run home-manager -- switch --flake .#me`
|
||||||
(afterwards just `home-manager switch --flake .#me` — it's installed)
|
(afterwards just `home-manager switch --flake .#me` — it's installed)
|
||||||
6. Clone/symlink this directory to `~/.nomarchy` (or export
|
6. Clone/symlink this directory to `~/.nomarchy` (or export
|
||||||
`NOMARCHY_PATH`) so `nomarchy-theme-sync` knows where the state lives.
|
`NOMARCHY_PATH`) so `nomarchy-state-sync` knows where the state lives.
|
||||||
|
|
||||||
Day-to-day:
|
Day-to-day:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nomarchy-theme-sync list # 24 shipped presets
|
nomarchy-state-sync list # 24 shipped presets
|
||||||
nomarchy-theme-sync apply gruvbox # writes state + home-manager switch
|
nomarchy-state-sync apply gruvbox # writes state + home-manager switch
|
||||||
nomarchy-theme-sync bg next # cycle wallpapers (instant, no rebuild)
|
nomarchy-state-sync bg next # cycle wallpapers (instant, no rebuild)
|
||||||
```
|
```
|
||||||
|
|
||||||
The system layer only needs `nixos-rebuild` when you change `system.nix`.
|
The system layer only needs `nixos-rebuild` when you change `system.nix`.
|
||||||
|
|||||||
@@ -29,10 +29,10 @@
|
|||||||
#
|
#
|
||||||
# Icon pack: Papirus ships by default (auto Dark/Light by theme mode) and
|
# Icon pack: Papirus ships by default (auto Dark/Light by theme mode) and
|
||||||
# is the ONLY icon pack in your closure. To use another, set the `icons`
|
# is the ONLY icon pack in your closure. To use another, set the `icons`
|
||||||
# field in your theme-state.json to a theme name from a known pack — only
|
# field in your state.json to a theme name from a known pack — only
|
||||||
# that pack is then added (no bloat otherwise). Known packs: Papirus,
|
# that pack is then added (no bloat otherwise). Known packs: Papirus,
|
||||||
# Tela, Qogir, Reversal, Numix (see modules/home/theme.nix `iconPacks`).
|
# Tela, Qogir, Reversal, Numix (see modules/home/theme.nix `iconPacks`).
|
||||||
# e.g. theme-state.json: "icons": "Tela-dark"
|
# e.g. state.json: "icons": "Tela-dark"
|
||||||
|
|
||||||
# ── Opt-in features — uncomment and tweak to enable ─────────────────
|
# ── Opt-in features — uncomment and tweak to enable ─────────────────
|
||||||
# nomarchy.nightlight = { # scheduled blue-light filter (hyprsunset)
|
# nomarchy.nightlight = { # scheduled blue-light filter (hyprsunset)
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ pkgs.testers.runNixOSTest {
|
|||||||
# PolicyKit test action so pkexec can raise the agent.
|
# PolicyKit test action so pkexec can raise the agent.
|
||||||
security.polkit.enable = true;
|
security.polkit.enable = true;
|
||||||
|
|
||||||
nomarchy.system.stateFile = flake + "/theme-state.json";
|
nomarchy.system.stateFile = flake + "/state.json";
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
||||||
"C /home/nomarchy/.nomarchy/theme-state.json 0644 nomarchy users - ${flake + "/theme-state.json"}"
|
"C /home/nomarchy/.nomarchy/state.json 0644 nomarchy users - ${flake + "/state.json"}"
|
||||||
];
|
];
|
||||||
services.greetd.settings.initial_session = {
|
services.greetd.settings.initial_session = {
|
||||||
command = "start-hyprland";
|
command = "start-hyprland";
|
||||||
@@ -49,7 +49,7 @@ pkgs.testers.runNixOSTest {
|
|||||||
home-manager.useGlobalPkgs = true;
|
home-manager.useGlobalPkgs = true;
|
||||||
home-manager.users.nomarchy = {
|
home-manager.users.nomarchy = {
|
||||||
imports = [ flake.homeModules.nomarchy ];
|
imports = [ flake.homeModules.nomarchy ];
|
||||||
nomarchy.stateFile = flake + "/theme-state.json";
|
nomarchy.stateFile = flake + "/state.json";
|
||||||
nomarchy.idle.enable = false;
|
nomarchy.idle.enable = false;
|
||||||
# Ensure pinentry-qt is the active pinentry (keys.nix default).
|
# Ensure pinentry-qt is the active pinentry (keys.nix default).
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ flake seed files (see pkgs/nomarchy-install/default.nix installPhase).
|
|||||||
|
|
||||||
The package copies a fixed file list (not a recursive tree copy):
|
The package copies a fixed file list (not a recursive tree copy):
|
||||||
|
|
||||||
flake.nix system.nix home.nix theme-state.json
|
flake.nix system.nix home.nix state.json
|
||||||
|
|
||||||
README.md and hardware-configuration.nix stay repo-only (docs /
|
README.md and hardware-configuration.nix stay repo-only (docs /
|
||||||
nixos-generate-config at install time) and are intentionally absent
|
nixos-generate-config at install time) and are intentionally absent
|
||||||
@@ -31,7 +31,7 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Must match the cp list in pkgs/nomarchy-install/default.nix installPhase.
|
# Must match the cp list in pkgs/nomarchy-install/default.nix installPhase.
|
||||||
SHIPPED = ("flake.nix", "system.nix", "home.nix", "theme-state.json")
|
SHIPPED = ("flake.nix", "system.nix", "home.nix", "state.json")
|
||||||
|
|
||||||
|
|
||||||
def fail(msg: str) -> None:
|
def fail(msg: str) -> None:
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ pkgs.testers.runNixOSTest {
|
|||||||
nomarchy.system.stateFile = flake + "/themes/${slug}.json";
|
nomarchy.system.stateFile = flake + "/themes/${slug}.json";
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
||||||
"C /home/nomarchy/.nomarchy/theme-state.json 0644 nomarchy users - ${flake + "/themes/${slug}.json"}"
|
"C /home/nomarchy/.nomarchy/state.json 0644 nomarchy users - ${flake + "/themes/${slug}.json"}"
|
||||||
];
|
];
|
||||||
services.greetd.settings.initial_session = {
|
services.greetd.settings.initial_session = {
|
||||||
command = "start-hyprland";
|
command = "start-hyprland";
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ def convert(theme_dir: Path) -> dict:
|
|||||||
"name": slug.replace("-", " ").title(),
|
"name": slug.replace("-", " ").title(),
|
||||||
"slug": slug,
|
"slug": slug,
|
||||||
"mode": mode,
|
"mode": mode,
|
||||||
# Empty = auto: nomarchy-theme-sync falls back to the first file in
|
# Empty = auto: nomarchy-state-sync falls back to the first file in
|
||||||
# the theme's backgrounds/ directory.
|
# the theme's backgrounds/ directory.
|
||||||
"wallpaper": "",
|
"wallpaper": "",
|
||||||
"colors": {
|
"colors": {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ pkgs.testers.runNixOSTest {
|
|||||||
nomarchy.system.stateFile = flake + "/themes/boreal.json";
|
nomarchy.system.stateFile = flake + "/themes/boreal.json";
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
||||||
"C /home/nomarchy/.nomarchy/theme-state.json 0644 nomarchy users - ${flake + "/themes/boreal.json"}"
|
"C /home/nomarchy/.nomarchy/state.json 0644 nomarchy users - ${flake + "/themes/boreal.json"}"
|
||||||
"f /run/nomarchy-test-lid-state 0644 nomarchy users - state:_closed"
|
"f /run/nomarchy-test-lid-state 0644 nomarchy users - state:_closed"
|
||||||
];
|
];
|
||||||
services.greetd.settings.initial_session = {
|
services.greetd.settings.initial_session = {
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
# screenshots into $out: desktop.png, menu.png (rofi root open), and
|
# screenshots into $out: desktop.png, menu.png (rofi root open), and
|
||||||
# btop.png (Kitty running btop, when the terminal starts under softGL).
|
# btop.png (Kitty running btop, when the terminal starts under softGL).
|
||||||
# Also asserts nomarchy-doctor-status JSON (class:bad) — the harness
|
# Also asserts nomarchy-doctor-status JSON (class:bad) — the harness
|
||||||
# seeds an untracked theme-state.json, so doctor always reports ✖.
|
# seeds an untracked state.json, so doctor always reports ✖.
|
||||||
# Maintainer tool, NOT a checks.* gate (a full-desktop VM is far too
|
# Maintainer tool, NOT a checks.* gate (a full-desktop VM is far too
|
||||||
# heavy for CI). Impure: THEME env picks the theme slug.
|
# heavy for CI). Impure: THEME env picks the theme slug.
|
||||||
# THEME=<slug> nix build --impure -f tools/theme-shot.nix --no-link --print-out-paths
|
# THEME=<slug> nix build --impure -f tools/theme-shot.nix --no-link --print-out-paths
|
||||||
# The guest gets a minimal ~/.nomarchy (just the theme JSON as
|
# The guest gets a minimal ~/.nomarchy (just the theme JSON as
|
||||||
# theme-state.json), seeded via tmpfiles BEFORE greetd starts the
|
# state.json), seeded via tmpfiles BEFORE greetd starts the
|
||||||
# session, so the session's own exec-once wallpaper paint works —
|
# session, so the session's own exec-once wallpaper paint works —
|
||||||
# assets come from the themesDir baked into the tool. Bar, menus AND
|
# assets come from the themesDir baked into the tool. Bar, menus AND
|
||||||
# wallpaper render.
|
# wallpaper render.
|
||||||
@@ -42,13 +42,13 @@ pkgs.testers.runNixOSTest {
|
|||||||
extraGroups = [ "wheel" "video" ];
|
extraGroups = [ "wheel" "video" ];
|
||||||
};
|
};
|
||||||
nomarchy.system.stateFile = flake + "/themes/${slug}.json";
|
nomarchy.system.stateFile = flake + "/themes/${slug}.json";
|
||||||
# Seed the runtime state (theme-sync reads $NOMARCHY_PATH, default
|
# Seed the runtime state (state-sync reads $NOMARCHY_PATH, default
|
||||||
# ~/.nomarchy) before greetd auto-starts the session, so the
|
# ~/.nomarchy) before greetd auto-starts the session, so the
|
||||||
# exec-once `nomarchy-theme-sync wallpaper` finds it and paints.
|
# exec-once `nomarchy-state-sync wallpaper` finds it and paints.
|
||||||
# NixOS activation creates /home/nomarchy before systemd runs.
|
# NixOS activation creates /home/nomarchy before systemd runs.
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
||||||
"C /home/nomarchy/.nomarchy/theme-state.json 0644 nomarchy users - ${flake + "/themes/${slug}.json"}"
|
"C /home/nomarchy/.nomarchy/state.json 0644 nomarchy users - ${flake + "/themes/${slug}.json"}"
|
||||||
];
|
];
|
||||||
services.greetd.settings.initial_session = {
|
services.greetd.settings.initial_session = {
|
||||||
command = "start-hyprland";
|
command = "start-hyprland";
|
||||||
|
|||||||
Reference in New Issue
Block a user