feat(menu): Recovery scopes (#111), System IA (#105), drop Control Center (#110)
Some checks failed
Check / eval (push) Has been cancelled

System is Connectivity / Devices / Recovery / Preferences under the same
six-entry root. Recovery lists desktop (HM), system boot, and BTRFS files
once each with cost labels. Control Center is gone: blur/gaps live in Look
& Feel; update checks and Bluetooth/Printing package toggles in Preferences
(with rebuilds). docs/RECOVERY paths updated.
This commit is contained in:
2026-07-15 11:49:57 +01:00
parent 0ba9633728
commit 856a9d1a49
14 changed files with 246 additions and 420 deletions

View File

@@ -75,7 +75,6 @@ Flat on purpose. Two module trees, one options file each, no hidden layers.
│ ├── nomarchy-state-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-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

View File

@@ -169,44 +169,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.
### 105. `[big]` System-menu information architecture
Keep exactly six root menu entries while reorganizing System into
Connectivity, Devices, Recovery, and Preferences. This item must be split into
scoped implementation/verification tasks before work starts. Pass = every
current route has one deliberate home, navigation remains shallow, and no
root-level entry is added or lost.
### 110. `[big]` Retire Control Center safely
Split this into two phases before implementation: first build a migration/drop
matrix for every unique setting, then remove the package and all references.
Keyboard and terminal replacements must reject unsafe free-text values. Pass =
no supported setting silently disappears and the second phase leaves no stale
launcher, menu, package, documentation, or generated-artifact reference.
**Facts for the matrix, from #116 (2026-07-14):** its Bluetooth and Printing
rows write `settings.{bluetooth,printing}.enable`, and those keys became live
bridges to the NixOS config in a9f3a64 — until then they wrote JSON nothing
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
(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
machine whose state.json already says `bluetooth.enable = false` will
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,
and if a key is deliberately retired, remove its case in the same commit.
Whatever inherits the rows should rebuild on toggle rather than print
"requires rebuild" (#117). Updates (`settings.updates.enable`) is HM-side,
where `nomarchy.settings` genuinely exists, and needs none of this.
### 111. Scope-first Recovery menu
Replace confusing Rollback/Snapshots duplication with explicit Desktop
generation, System boot generation, and Files/root BTRFS scopes. Pass = labels
state what is restored and from where before action, with destructive or reboot
effects made clear and each existing recovery path represented once.
## LATER ## LATER
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided, - **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
@@ -219,8 +181,7 @@ effects made clear and each existing recovery path represented once.
- **MIPI/IPU software-ISP camera** support (no-UVC machines). - **MIPI/IPU software-ISP camera** support (no-UVC machines).
- **NixOS release bump → v2** `[human]`: deliberate, hand-edited, never - **NixOS release bump → v2** `[human]`: deliberate, hand-edited, never
automated; the previous attempt was discarded (2026-06-22) over a automated; the previous attempt was discarded (2026-06-22) over a
Hyprland OOM blocker — see MEMORY.md before retrying (NOW#3 should Hyprland OOM blocker — see MEMORY.md before retrying.
also soften that blocker class).
## FUTURE (decided deferred — not the agent queue head) ## FUTURE (decided deferred — not the agent queue head)
@@ -255,25 +216,6 @@ high-ROI, etc.) live in the journal + ROADMAP — not here.*
### Product / day-2 ### Product / day-2
### 117. Control Center says "requires rebuild" and leaves the user to do it
**Filed as an input to #110 (Retire Control Center safely), not as standalone
work** — Bernardo 2026-07-14: the Control Center is going away, so fixing its
toggles would be building on a condemned surface. Recorded because the
observation outlives the tool: it is a fact the #110 migration matrix needs,
and whatever menu inherits these settings must not repeat it.
Noticed while fixing #116 (2026-07-14). `nomarchy-control-center.sh`'s
Bluetooth, Printing and Updates toggles set the state and print "…​ (requires
rebuild)", so the setting only lands whenever the user next thinks to run
`nomarchy-rebuild`. Every menu toggle written since does the rebuild itself
and toasts the outcome (`nomarchy-autologin`, `nomarchy-fingerprint`,
`nomarchy-autotimezone`) — the state-write-then-rebuild shape. A user who
toggles Bluetooth off and sees Bluetooth still running cannot tell "needs a
rebuild" from "the toggle is broken again", which is exactly the symptom #116
removed. Migrating these rows into the rofi menu resolves it for free, since
that shape is the convention there.
### 114. Greeter ignores per-device keyboard layouts ### 114. Greeter ignores per-device keyboard layouts
Found by Bernardo 2026-07-14: logging out while docked lands on tuigreet, Found by Bernardo 2026-07-14: logging out while docked lands on tuigreet,
@@ -321,7 +263,6 @@ See **VISION**. Open PROPOSED: post-install hardware hints; NVIDIA
deferred past v1; IR portal (b)/(c) need T14s (HARDWARE-QUEUE § T14s). deferred past v1; IR portal (b)/(c) need T14s (HARDWARE-QUEUE § T14s).
Standing calls: browser = Chromium; power = PPD. Standing calls: browser = Chromium; power = PPD.
## Decisions `[human]` ## Decisions `[human]`
Open calls only Bernardo can make; agents add options/evidence but never Open calls only Bernardo can make; agents add options/evidence but never

View File

@@ -19,6 +19,18 @@ Template:
--- ---
## 2026-07-15 — #111 Recovery + #105 System IA + #110 retire Control Center
- **Task:** Scope-first Recovery, System submenu IA, retire Control Center.
- **Did:** System → Connectivity / Devices / Recovery / Preferences. Recovery:
Desktop gen (HM list+activate), System boot (toast + RECOVERY.md), Files
BTRFS, What changed. Migrated CC: blur/gaps presets in Look & Feel; update
checks + Bluetooth/Printing package toggles in Preferences (rebuild in
terminal). Removed `pkgs/nomarchy-control-center` + menu entry. Docs
RECOVERY/HARDWARE/VISION updated. Closed #111 #105 #110 #117.
- **Verified:** V0 menu-back; HM gen builds; flake check --no-build.
- **Pending:** V3 eyeball the new menu tree on a real session.
- **Next suggestion:** #115/#120 human decisions, or hardware queue.
## 2026-07-15 — V2 permanent checks for #104 / #122 ## 2026-07-15 — V2 permanent checks for #104 / #122
- **Task:** V2 everything VM-checkable from the #104/#122 batch. - **Task:** V2 everything VM-checkable from the #104/#122 batch.
- **Did:** Extracted `pkgs/nomarchy-airplane` (overlay). Added - **Did:** Extracted `pkgs/nomarchy-airplane` (overlay). Added

View File

@@ -136,10 +136,9 @@ confirm → `fwupdmgr update`. It never auto-flashes — `fwupdmgr update`
confirms each device and prompts for the reboot a capsule needs; pillar 1 confirms each device and prompts for the reboot a capsule needs; pillar 1
(rock-stable) forbids silent BIOS writes. (rock-stable) forbids silent BIOS writes.
**Hints (#43 / #73):** MOTD cheat-sheet line + `nomarchy-control-center **Hints (#43 / #73):** MOTD cheat-sheet line when `fwupdmgr` is on PATH.
--first-boot` tip when `fwupdmgr` is on PATH. Fingerprint / doctor tips Fingerprint / doctor tips follow the same pattern (fingerprint MOTD only
follow the same pattern (fingerprint MOTD only when when `nomarchy.hardware.fingerprint.enable`).
`nomarchy.hardware.fingerprint.enable`).
**Still queued:** a Doctor “updates available” check, and **Still queued:** a Doctor “updates available” check, and
Waybar/updates-panel integration. Waybar/updates-panel integration.

View File

@@ -21,8 +21,10 @@ home-manager generations # newest first, one per theme/HM change
/nix/store/…-home-manager-generation/activate # run the one you want /nix/store/…-home-manager-generation/activate # run the one you want
``` ```
The same picker lives in the menu: **System Rollback** lists the The same picker lives in the menu: **System Recovery Desktop
recent desktop generations — pick one and it activates. generation** lists the recent Home Manager generations — pick one and
it activates. System-level undo is **System Recovery System boot
generation** (boot menu) or **Files (BTRFS)** (snapper).
Or simply apply a theme you know is good: `nomarchy-state-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
@@ -69,8 +71,8 @@ With `nomarchy.system.snapper.enable` (the installer's default on
BTRFS), the root filesystem has hourly/daily history, and BTRFS), the root filesystem has hourly/daily history, and
`nixos-rebuild-snap` leaves a snapshot right before a rebuild: `nixos-rebuild-snap` leaves a snapshot right before a rebuild:
- **GUI:** menu System Snapshots (btrfs-assistant; expects a polkit - **GUI:** menu System Recovery Files (BTRFS) (btrfs-assistant;
password prompt). expects a polkit password prompt).
- **Terminal/SSH:** `sudo nomarchy-snapshots` — browse a snapshot's - **Terminal/SSH:** `sudo nomarchy-snapshots` — browse a snapshot's
diff, **restore changed files** (snapper `undochange`), or **roll the diff, **restore changed files** (snapper `undochange`), or **roll the
whole root back** to a snapshot and reboot. Both destructive actions whole root back** to a snapshot and reboot. Both destructive actions

View File

@@ -427,6 +427,13 @@ 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.
- ✓ **Recovery + System IA + Control Center retired (#111 / #105 / #110):**
System menu is Connectivity / Devices / Recovery / Preferences (root still
six entries). Recovery is scope-first: Desktop generation (HM), System boot
generation (how), Files BTRFS, What changed. Control Center package removed;
blur/gaps (presets) in Look & Feel; update checks + Bluetooth/Printing
package toggles in Preferences (system toggles run `nomarchy-rebuild`).
#117 absorbed (rebuild no longer left to the user for those rows).
- ✓ **Airplane mode (#104):** System Airplane mode + SUPER+CTRL+R + - ✓ **Airplane mode (#104):** System Airplane mode + SUPER+CTRL+R +
Waybar plane glyph (self-hides when off). Runtime `nomarchy-airplane` Waybar plane glyph (self-hides when off). Runtime `nomarchy-airplane`
softs Wi-Fi (nmcli) + Bluetooth (rfkill/bluetoothctl), remembers prior softs Wi-Fi (nmcli) + Bluetooth (rfkill/bluetoothctl), remembers prior

View File

@@ -74,7 +74,7 @@ Reference: [HARDWARE.md](HARDWARE.md) §4§5, §11 (doctor).
| **Silent first-week card** ✓ | One dismissible “youre 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 “youre 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 `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 tips; first-session toast is #81; still refine self-gated hardware hints if needed |
--- ---

View File

@@ -85,7 +85,6 @@
themesDir = ./themes; themesDir = ./themes;
}; };
nomarchy-doctor = final.callPackage ./pkgs/nomarchy-doctor { }; nomarchy-doctor = final.callPackage ./pkgs/nomarchy-doctor { };
nomarchy-control-center = final.callPackage ./pkgs/nomarchy-control-center { };
nomarchy-battery-notify = final.callPackage ./pkgs/nomarchy-battery-notify { }; nomarchy-battery-notify = final.callPackage ./pkgs/nomarchy-battery-notify { };
nomarchy-first-boot = final.callPackage ./pkgs/nomarchy-first-boot { }; nomarchy-first-boot = final.callPackage ./pkgs/nomarchy-first-boot { };
nomarchy-what-changed = final.callPackage ./pkgs/nomarchy-what-changed { }; nomarchy-what-changed = final.callPackage ./pkgs/nomarchy-what-changed { };
@@ -136,7 +135,6 @@
# (maintainer/CI convenience — modules install them via the overlay). # (maintainer/CI convenience — modules install them via the overlay).
nomarchy-doctor = pkgs.nomarchy-doctor; nomarchy-doctor = pkgs.nomarchy-doctor;
nomarchy-lifecycle = pkgs.nomarchy-lifecycle; nomarchy-lifecycle = pkgs.nomarchy-lifecycle;
nomarchy-control-center = pkgs.nomarchy-control-center;
nomarchy-battery-notify = pkgs.nomarchy-battery-notify; nomarchy-battery-notify = pkgs.nomarchy-battery-notify;
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;

View File

@@ -341,7 +341,7 @@ let
done done
back back
} | rofi_menu -show-icons -p "profile (now: $cur)") || exit 0 } | rofi_menu -show-icons -p "profile (now: $cur)") || exit 0
[ "$choice" = "$BACK" ] && exec "$0" system [ "$choice" = "$BACK" ] && exec "$0" system-preferences
[ -n "$choice" ] && powerprofilesctl set "$choice" ;; [ -n "$choice" ] && powerprofilesctl set "$choice" ;;
powermgmt) powermgmt)
@@ -548,7 +548,7 @@ ${themeRows}
back back
} | rofi_menu -show-icons -p Audio) || exit 0 } | rofi_menu -show-icons -p Audio) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" system ;; "$BACK") exec "$0" system-devices ;;
"Send output"*) "Send output"*)
nomarchy-dock-audio select menu ;; nomarchy-dock-audio select menu ;;
*Output*) exec rofi-pulse-select sink ;; *Output*) exec rofi-pulse-select sink ;;
@@ -567,7 +567,7 @@ ${themeRows}
done done
back back
} | rofi_menu -p "Keyboard") || exit 0 } | rofi_menu -p "Keyboard") || exit 0
[ "$picked" = "$BACK" ] && exec "$0" system [ "$picked" = "$BACK" ] && exec "$0" system-devices
device=''${picked%% ·*} device=''${picked%% ·*}
saved=$(nomarchy-keyboard-layout saved "$device") saved=$(nomarchy-keyboard-layout saved "$device")
layout=$( { layout=$( {
@@ -627,7 +627,7 @@ ${themeRows}
printf '%s' "$mons" | jq -r '.[] | "\(.name) · \(.width)x\(.height)@\(.refreshRate|round)Hz"' printf '%s' "$mons" | jq -r '.[] | "\(.name) · \(.width)x\(.height)@\(.refreshRate|round)Hz"'
printf '%s\n' "$BACK" printf '%s\n' "$BACK"
} | rofi_menu -p Display ) || exit 0 } | rofi_menu -p Display ) || exit 0
[ "$name" = "$BACK" ] && exec "$0" system [ "$name" = "$BACK" ] && exec "$0" system-devices
[ "$name" = "Profiles " ] && exec "$0" display-profile [ "$name" = "Profiles " ] && exec "$0" display-profile
case "$name" in case "$name" in
"Dock mode"*) "Dock mode"*)
@@ -754,16 +754,13 @@ ${themeRows}
esac ;; esac ;;
lookfeel) lookfeel)
# Look & Feel appearance: Theme, wallpapers, night light. # Look & Feel appearance: Theme, wallpapers, night light, blur/gaps
# Root stays six entries (Apps / Look & Feel / Tools / System / # (#110 moved blur/gaps out of Control Center presets only, no
# Power / Keybindings). SUPER+T / SUPER+SHIFT+T still hit leaves. # free-text). Root stays six entries.
choice=$( { choice=$( {
row "Theme${menuHint "theme"}" preferences-desktop-theme row "Theme${menuHint "theme"}" preferences-desktop-theme
row "Next wallpaper${hintForAction "exec, nomarchy-state-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
# hyprsunset-only check always showed (off) in geo mode and when
# the unit was still the empty HM mask from a disabled package unit.
if [ "$(nomarchy-nightlight is-active 2>/dev/null)" = on ] if [ "$(nomarchy-nightlight is-active 2>/dev/null)" = on ]
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
@@ -772,6 +769,12 @@ ${themeRows}
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
case "$(nomarchy-state-sync get ui.blur 2>/dev/null)" in
true|True) row "Blur (on)" preferences-desktop-effects ;;
*) row "Blur (off)" preferences-desktop-effects ;;
esac
gaps=$(nomarchy-state-sync get ui.gapsOut 2>/dev/null || echo 12)
row "Gaps (now $gaps px)" preferences-desktop-display
back back
} | rofi_menu -show-icons -markup-rows -p "Look & Feel") || exit 0 } | rofi_menu -show-icons -markup-rows -p "Look & Feel") || exit 0
case "$choice" in case "$choice" in
@@ -781,8 +784,43 @@ ${themeRows}
*"Next wallpaper"*) exec nomarchy-state-sync bg next ;; *"Next wallpaper"*) exec nomarchy-state-sync bg next ;;
*"Reset wallpaper"*) exec nomarchy-state-sync bg auto ;; *"Reset wallpaper"*) exec nomarchy-state-sync bg auto ;;
*"Night light"*) exec "$0" nightlight ;; *"Night light"*) exec "$0" nightlight ;;
*Blur*) exec "$0" blur ;;
*Gaps*) exec "$0" gaps ;;
esac ;; esac ;;
blur)
# ui.blur Hyprland decoration; needs a home switch to bake.
case "$(nomarchy-state-sync get ui.blur 2>/dev/null)" in
true|True)
nomarchy-state-sync --quiet set ui.blur false
notify-send "Blur" "Off desktop rebuilt." ;;
*)
nomarchy-state-sync --quiet set ui.blur true
notify-send "Blur" "On desktop rebuilt." ;;
esac
exit 0 ;;
gaps)
# Preset-only (no free-text) #110 keyboard/gaps free-text was unsafe.
cur=$(nomarchy-state-sync get ui.gapsOut 2>/dev/null || echo 12)
choice=$( {
for g in 0 4 8 12 16 24; do
if [ "$g" = "$cur" ]; then row "$g px (current)" preferences-desktop-display
else row "$g px" preferences-desktop-display
fi
done
back
} | rofi_menu -show-icons -p "Outer gaps (now $cur px)") || exit 0
case "$choice" in
"$BACK") exec "$0" lookfeel ;;
*)
g=''${choice%% *}
case "$g" in *[!0-9]*) exit 0 ;; esac
nomarchy-state-sync --quiet set ui.gapsOut "$g"
notify-send "Gaps" "Outer gaps $g px desktop rebuilt." ;;
esac
exit 0 ;;
display-profile) display-profile)
# Named multi-output layouts (nomarchy.displayProfiles). Applying is # Named multi-output layouts (nomarchy.displayProfiles). Applying is
# instant (hyprctl per entry, via nomarchy-display-profile) and # instant (hyprctl per entry, via nomarchy-display-profile) and
@@ -1039,7 +1077,7 @@ ${themeRows}
back back
} | rofi_menu -show-icons -p Fingerprint) || exit 0 } | rofi_menu -show-icons -p Fingerprint) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" system ;; "$BACK") exec "$0" system-devices ;;
*Enroll*) *Enroll*)
exec ${cfg.terminal} -e sh -c ' exec ${cfg.terminal} -e sh -c '
echo "== Enroll fingerprint for $USER ==" echo "== Enroll fingerprint for $USER =="
@@ -1076,10 +1114,6 @@ ${themeRows}
exec ${cfg.terminal} -e nomarchy-fingerprint toggle ;; exec ${cfg.terminal} -e nomarchy-fingerprint toggle ;;
esac ;; esac ;;
controlcenter)
# Nomarchy TUI Control Center.
exec ${cfg.terminal} -e nomarchy-control-center ;;
whatchanged) whatchanged)
# Plain-language last rebuild (system + desktop nvd). Toast the # Plain-language last rebuild (system + desktop nvd). Toast the
# one-liners; open a floating terminal for the full report when # one-liners; open a floating terminal for the full report when
@@ -1094,46 +1128,66 @@ ${themeRows}
-e sh -c "nomarchy-what-changed -e sh -c "nomarchy-what-changed
printf '\nEnter to close.'; read -r _" ;; printf '\nEnter to close.'; read -r _" ;;
rollback) # #111 Scope-first Recovery one menu for every undo path.
# Undo, one menu away (informative + rock-stable). Desktop = # Desktop = Home Manager generations (themes/home config).
# Home Manager generations: theme/config history is already one # System boot = NixOS generations in the firmware boot menu.
# generation per change, and running an older generation's # Files = BTRFS snapper (files on disk / whole-root roll).
# activate script is the supported HM rollback reversible by # Each path is listed once; labels say what is restored and the cost
# activating a newer one again, so no typed-yes gate here. # (instant / reboot / confirm) before the action.
# System-level undo deliberately LINKS OUT instead of putting recovery)
# destructive flows in rofi: boot-menu generations and snapper choice=$( {
# rollback have their own homes and gates (docs/RECOVERY.md, row "Desktop generation themes & home config (instant, reversible)" document-open-recent
# nomarchy-snapshots). row "System boot generation older NixOS (reboot, then pick in boot menu)" system-reboot
command -v nomarchy-snapshots >/dev/null 2>&1 \
&& row "Files (BTRFS) restore files or roll root (password + confirm)" timeshift
command -v nomarchy-what-changed >/dev/null 2>&1 \
&& row "What changed? last system & desktop rebuild" view-refresh
back
} | rofi_menu -show-icons -markup-rows -p Recovery) || exit 0
case "$choice" in
"$BACK") exec "$0" system ;;
*Desktop*) exec "$0" recovery-desktop ;;
*System\ boot*)
notify-send "System boot generation" \
"Reboot and pick an older NixOS generation in the boot menu (last 10 kept). That boot is temporary make it stick: revert in ~/.nomarchy, then nomarchy-rebuild. Details: docs/RECOVERY.md §3." ;;
*Files*) exec "$0" snapshot ;;
*"What changed"*) exec "$0" whatchanged ;;
esac ;;
recovery-desktop)
# Home Manager generations only desktop half of RECOVERY.md §1.
gens=$(home-manager generations 2>/dev/null | head -10) gens=$(home-manager generations 2>/dev/null | head -10)
choice=$( { choice=$( {
if [ -n "$gens" ]; then if [ -n "$gens" ]; then
printf '%s\n' "$gens" | awk '{ printf '%s\n' "$gens" | awk '{
printf "Desktop gen %s %s %s%s\n", $5, $1, $2, (NR==1 ? " (current)" : "") printf "Gen %s %s %s%s\n", $5, $1, $2, (NR==1 ? " (current)" : "")
}' | while IFS= read -r line; do row "$line" document-open-recent; done }' | while IFS= read -r line; do row "$line" document-open-recent; done
else
row "(no Home Manager generations found)" dialog-information
fi fi
command -v nomarchy-snapshots >/dev/null 2>&1 \
&& row "System files Snapshots" timeshift
row "System config boot an older generation (how)" help-about
back back
} | rofi_menu -show-icons -p Rollback) || exit 0 } | rofi_menu -show-icons -p "Desktop generation") || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" system ;; "$BACK"|"(no "*) exec "$0" recovery ;;
"Desktop gen "*) "Gen "*)
num=''${choice#Desktop gen }; num=''${num%% *} num=''${choice#Gen }; num=''${num%% *}
path=$(printf '%s\n' "$gens" | awk -v n="$num" '$5 == n { print $7 }') path=$(printf '%s\n' "$gens" | awk -v n="$num" '$5 == n { print $7 }')
[ -x "$path/activate" ] \ [ -x "$path/activate" ] \
|| { notify-send "Rollback" "Generation $num not found on disk."; exit 1; } || { notify-send "Desktop generation" "Generation $num not found on disk."; exit 1; }
exec ${cfg.terminal} -e sh -c " exec ${cfg.terminal} -e sh -c "
echo \"Activating Home Manager generation $num\"; echo echo \"Activating Home Manager generation $num (desktop only)\"
echo \"This does not change the NixOS system or BTRFS files.\"
echo
\"$path/activate\" \"$path/activate\"
echo; echo \"Desktop rolled back to generation $num.\" echo
echo \"(Roll forward the same way newer generations stay listed.)\" echo \"Desktop is now generation $num.\"
echo \"Roll forward the same way newer generations stay listed.\"
printf 'Enter to close.'; read -r _" ;; printf 'Enter to close.'; read -r _" ;;
*Snapshots*) exec "$0" snapshot ;;
*"older generation"*)
notify-send "System rollback" "Reboot and pick an older NixOS generation in the boot menu (the last 10 are kept). Make it stick: revert the change in ~/.nomarchy, then nomarchy-rebuild. Details: docs/RECOVERY.md §3." ;;
esac ;; esac ;;
# Compat aliases old menu ids and muscle memory.
rollback) exec "$0" recovery ;;
tools) tools)
# Tools submenu utilities you invoke. Each leaf is also reachable # Tools submenu utilities you invoke. Each leaf is also reachable
# directly (SUPER+CTRL+<mnemonic>); this just keeps the root short. # directly (SUPER+CTRL+<mnemonic>); this just keeps the root short.
@@ -1170,37 +1224,79 @@ ${themeRows}
esac ;; esac ;;
system) system)
# System submenu connectivity and machine state. Snapshots and the # #105 System IA four groups under the same System entry so the
# power-profile picker self-gate (BTRFS+snapper / laptop on ppd), so # root menu stays at six items (Apps / Look & Feel / Tools / System /
# the submenu shrinks to match the hardware, like the Waybar modules. # Power / Keybindings). Leaves keep SUPER+CTRL mnemonics via their
# own modules; this only organizes the picker.
choice=$( {
row "Connectivity" network-wireless
row "Devices" preferences-desktop-peripherals
row "Recovery${menuHint "recovery"}" edit-undo
row "Preferences" preferences-system
back
} | rofi_menu -show-icons -markup-rows -p System) || exit 0
case "$choice" in
"$BACK") exec "$0" ;;
*Connectivity*) exec "$0" system-connectivity ;;
*Devices*) exec "$0" system-devices ;;
*Recovery*) exec "$0" recovery ;;
*Preferences*) exec "$0" system-preferences ;;
esac ;;
system-connectivity)
choice=$( { choice=$( {
row "Network${menuHint "network"}" network-wireless row "Network${menuHint "network"}" network-wireless
row "VPN" network-vpn row "VPN" network-vpn
# Airplane mode (#104): runtime Wi-Fi+BT kill-switch. Label
# tracks the session flag; tool is always on PATH via airplane.nix.
if [ "$(nomarchy-airplane is-active 2>/dev/null)" = on ] if [ "$(nomarchy-airplane is-active 2>/dev/null)" = on ]
then row "Airplane mode (on)${menuHint "airplane"}" network-wireless-offline then row "Airplane mode (on)${menuHint "airplane"}" network-wireless-offline
else row "Airplane mode (off)${menuHint "airplane"}" network-wireless-offline else row "Airplane mode (off)${menuHint "airplane"}" network-wireless-offline
fi fi
command -v blueman-manager >/dev/null 2>&1 \ command -v blueman-manager >/dev/null 2>&1 \
&& row "Bluetooth${menuHint "bluetooth"}" bluetooth && row "Bluetooth${menuHint "bluetooth"}" bluetooth
back
} | rofi_menu -show-icons -markup-rows -p Connectivity) || exit 0
case "$choice" in
"$BACK") exec "$0" system ;;
*Network*) exec "$0" network ;;
*VPN*) exec "$0" vpn ;;
*"Airplane mode"*) exec "$0" airplane ;;
*Bluetooth*) exec "$0" bluetooth ;;
esac ;;
system-devices)
choice=$( {
[ -e "''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/pulse/native" ] \ [ -e "''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/pulse/native" ] \
&& row "Audio" audio-volume-high && row "Audio" audio-volume-high
row "Display" video-display row "Display" video-display
command -v nomarchy-keyboard-layout >/dev/null 2>&1 \ command -v nomarchy-keyboard-layout >/dev/null 2>&1 \
&& 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
command -v fprintd-list >/dev/null 2>&1 \
&& row "Fingerprint" preferences-desktop-user-password
command -v fwupdmgr >/dev/null 2>&1 && row "Firmware" firmware-manager
back
} | rofi_menu -show-icons -markup-rows -p Devices) || exit 0
case "$choice" in
"$BACK") exec "$0" system ;;
*Audio*) exec "$0" audio ;;
*Display*) exec "$0" display ;;
*Keyboard*) exec "$0" keyboard ;;
*Printers*) exec "$0" printers ;;
*Fingerprint*) exec "$0" fingerprint ;;
*Firmware*) exec "$0" firmware ;;
esac ;;
system-preferences)
# Preferences + former Control Center system toggles (#110).
# Bluetooth/Printing flip NixOS bridges rebuild in a terminal.
# Updates is HM-side (state-sync rebuilds home).
choice=$( {
row "Do Not Disturb${menuHint "dnd"}" notification-disabled row "Do Not Disturb${menuHint "dnd"}" notification-disabled
if [ "$(nomarchy-state-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
# Auto-login self-gated on the tool (greeter.enable). Off = the
# 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
# what an older state-sync printed for null accepted so a menu
# from a newer generation can't misreport against an older tool.
case "$(nomarchy-state-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 ;;
@@ -1212,57 +1308,83 @@ ${themeRows}
else row "Auto-commit (off)" git else row "Auto-commit (off)" git
fi fi
fi fi
command -v nomarchy-snapshots >/dev/null 2>&1 && row "Snapshots" timeshift case "$(nomarchy-state-sync get settings.updates.enable 2>/dev/null)" in
row "Rollback" edit-undo true|True) row "Update checks (on)" system-software-update ;;
# Plain-language last-rebuild summary (nvd; BACKLOG #82). *) row "Update checks (off)" system-software-update ;;
command -v nomarchy-what-changed >/dev/null 2>&1 \ esac
&& row "What changed?" view-refresh case "$(nomarchy-state-sync get settings.bluetooth.enable 2>/dev/null)" in
command -v nomarchy-doctor >/dev/null 2>&1 && row "Doctor" utilities-system-monitor false|False) row "Bluetooth package (off)" bluetooth ;;
# Firmware/LVFS updates self-gated on fwupd (default-on, but off *) row "Bluetooth package (on)" bluetooth ;;
# on VMs/headless via services.fwupd.enable=false), like Printers. esac
command -v fwupdmgr >/dev/null 2>&1 && row "Firmware" firmware-manager case "$(nomarchy-state-sync get settings.printing.enable 2>/dev/null)" in
# Fingerprint self-gated on fprintd CLI (services.fprintd; BACKLOG #55). true|True) row "Printing (on)" printer ;;
command -v fprintd-list >/dev/null 2>&1 && row "Fingerprint" preferences-desktop-user-password *) row "Printing (off)" printer ;;
command -v nomarchy-control-center >/dev/null 2>&1 && row "Control Center" preferences-desktop esac
if has_system_battery && command -v powerprofilesctl >/dev/null 2>&1; then if has_system_battery && command -v powerprofilesctl >/dev/null 2>&1; then
row "Power profile" preferences-system-power row "Power profile" preferences-system-power
fi fi
# Gated on a battery (like Power profile), NOT on the
# charge-threshold sysfs node: firmware without the control must
# get the leaf's explanation, not a silently missing row that
# reads as a broken feature (BACKLOG #96, Acer M5-481T).
has_system_battery && row "Battery limit" battery-080 has_system_battery && row "Battery limit" battery-080
command -v nomarchy-doctor >/dev/null 2>&1 && row "Doctor" utilities-system-monitor
back back
} | rofi_menu -show-icons -markup-rows -p System) || exit 0 } | rofi_menu -show-icons -markup-rows -p Preferences) || exit 0
case "$choice" in case "$choice" in
"$BACK") exec "$0" ;; "$BACK") exec "$0" system ;;
*Network*) exec "$0" network ;;
*VPN*) exec "$0" vpn ;;
*"Airplane mode"*) exec "$0" airplane ;;
*Bluetooth*) exec "$0" bluetooth ;;
*Audio*) exec "$0" audio ;;
*Display*) exec "$0" display ;;
*Keyboard*) exec "$0" keyboard ;;
*Printers*) exec "$0" printers ;;
*"Do Not Disturb"*) exec "$0" dnd ;; *"Do Not Disturb"*) exec "$0" dnd ;;
*"Auto timezone"*) exec "$0" autotimezone ;; *"Auto timezone"*) exec "$0" autotimezone ;;
*"Auto-login"*) exec "$0" autologin ;; *"Auto-login"*) exec "$0" autologin ;;
*"Auto-commit"*) exec "$0" autocommit ;; *"Auto-commit"*) exec "$0" autocommit ;;
*Snapshots*) exec "$0" snapshot ;; *"Update checks"*) exec "$0" updates-toggle ;;
*Rollback*) exec "$0" rollback ;; *"Bluetooth package"*) exec "$0" bluetooth-package ;;
*"What changed"*) exec "$0" whatchanged ;; *Printing*) exec "$0" printing-toggle ;;
*Doctor*) exec "$0" doctor ;;
*Firmware*) exec "$0" firmware ;;
*Fingerprint*) exec "$0" fingerprint ;;
*"Control Center"*) exec "$0" controlcenter ;;
*"Power profile"*) exec "$0" power-profile ;; *"Power profile"*) exec "$0" power-profile ;;
*"Battery limit"*) exec "$0" batterylimit ;; *"Battery limit"*) exec "$0" batterylimit ;;
*Doctor*) exec "$0" doctor ;;
esac ;; esac ;;
updates-toggle)
# settings.updates.enable HM nomarchy.updates (Waybar checker).
case "$(nomarchy-state-sync get settings.updates.enable 2>/dev/null)" in
true|True)
nomarchy-state-sync --quiet set settings.updates.enable false
notify-send "Update checks" "Off desktop rebuilt." ;;
*)
nomarchy-state-sync --quiet set settings.updates.enable true
notify-send "Update checks" "On desktop rebuilt." ;;
esac
exit 0 ;;
bluetooth-package)
# settings.bluetooth.enable NixOS hardware.bluetooth (state bridge).
# Needs a system rebuild; run it in a terminal with a clear story.
case "$(nomarchy-state-sync get settings.bluetooth.enable 2>/dev/null)" in
false|False) new=true; label=On ;;
*) new=false; label=Off ;;
esac
exec ${cfg.terminal} -e sh -c "
nomarchy-state-sync --quiet set settings.bluetooth.enable $new --no-switch
echo \"Bluetooth package $label (system rebuild)\"
echo
nomarchy-rebuild
echo
printf 'Enter to close.'; read -r _" ;;
printing-toggle)
case "$(nomarchy-state-sync get settings.printing.enable 2>/dev/null)" in
true|True) new=false; label=Off ;;
*) new=true; label=On ;;
esac
exec ${cfg.terminal} -e sh -c "
nomarchy-state-sync --quiet set settings.printing.enable $new --no-switch
echo \"Printing $label (system rebuild)\"
echo
nomarchy-rebuild
echo
printf 'Enter to close.'; read -r _" ;;
batterylimit) batterylimit)
# Battery charge-limit preset picker the rofi-System counterpart of # Battery charge-limit preset picker the rofi-System counterpart of
# the control-center's toggle (Bernardo: he wanted this in the menu, # (Bernardo: charge limit belongs in the menu, not a raw TUI). Writes
# not buried in the gum TUI). Writes the baked NixOS option # the baked NixOS option
# Persist + live sysfs apply (same as powermgmt). Boot/AC re-apply # Persist + live sysfs apply (same as powermgmt). Boot/AC re-apply
# stays with the oneshot in power.nix. # stays with the oneshot in power.nix.
has_charge_threshold \ has_charge_threshold \
@@ -1322,7 +1444,7 @@ ${themeRows}
fi fi
} }
case "$sel" in case "$sel" in
"$BACK") exec "$0" system ;; "$BACK") exec "$0" system-preferences ;;
# Order: Off (100%) before 90/80/60 so "100%" does not hit *0%* patterns. # Order: Off (100%) before 90/80/60 so "100%" does not hit *0%* patterns.
*Off*|*100%*) set_limit null "Off charges to 100%" ;; *Off*|*100%*) set_limit null "Off charges to 100%" ;;
*90%*) set_limit 90 "Set to 90%" ;; *90%*) set_limit 90 "Set to 90%" ;;
@@ -1363,7 +1485,7 @@ ${themeRows}
esac ;; esac ;;
*) *)
echo "usage: nomarchy-menu [lookfeel|tools|system|power|power-profile|powermgmt|batterylimit|theme|clipboard|calc|files|emoji|web|network|bluetooth|airplane|audio|display|display-profile|keyboard|printers|capture|colorpicker|keybinds|ask|dnd|nightlight|autotimezone|autologin|autocommit|vpn|snapshot|doctor|firmware|fingerprint|controlcenter|rollback|whatchanged]" >&2 echo "usage: nomarchy-menu [lookfeel|tools|system|system-connectivity|system-devices|system-preferences|recovery|power|power-profile|powermgmt|batterylimit|theme|clipboard|calc|files|emoji|web|network|bluetooth|airplane|audio|display|display-profile|keyboard|printers|capture|colorpicker|keybinds|ask|dnd|nightlight|autotimezone|autologin|autocommit|vpn|snapshot|doctor|firmware|fingerprint|rollback|whatchanged|blur|gaps|updates-toggle|bluetooth-package|printing-toggle]" >&2
exit 64 ;; exit 64 ;;
esac esac
''; '';

View File

@@ -21,7 +21,7 @@ let
install -Dm444 ${./branding/Nomarchy.ttf} $out/share/fonts/truetype/Nomarchy.ttf install -Dm444 ${./branding/Nomarchy.ttf} $out/share/fonts/truetype/Nomarchy.ttf
''; '';
# Control Center's Bluetooth toggle writes settings.bluetooth.enable; read # Menu Preferences Bluetooth package 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
# state-read.nix rather than a raw stack. null = key absent, which # state-read.nix rather than a raw stack. null = key absent, which
@@ -375,7 +375,6 @@ in
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
nomarchy-state-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-detect-hw # post-install hardware re-probe (HARDWARE.md §8) nomarchy-detect-hw # post-install hardware re-probe (HARDWARE.md §8)
# Day-to-day lifecycle (README §3): nomarchy-pull / -rebuild / -home # Day-to-day lifecycle (README §3): nomarchy-pull / -rebuild / -home

View File

@@ -52,7 +52,7 @@
audio.enable = lib.mkEnableOption "the Pipewire audio stack" // { default = true; }; audio.enable = lib.mkEnableOption "the Pipewire audio stack" // { default = true; };
# default stays a plain `true` here; ./default.nix mkDefaults it from # default stays a plain `true` here; ./default.nix mkDefaults it from
# settings.bluetooth.enable (Control Center's Bluetooth toggle). Reading # settings.bluetooth.enable (menu Preferences Bluetooth package). Reading
# the state in the option default is the trap ROADMAP § "NixOS-side state # the state in the option default is the trap ROADMAP § "NixOS-side state
# bridges (#116)" documents: `config.nomarchy.settings` does not exist on # bridges (#116)" documents: `config.nomarchy.settings` does not exist on
# the NixOS side, and `or true` silently swallowed that for years. # the NixOS side, and `or true` silently swallowed that for years.

View File

@@ -8,7 +8,7 @@
let let
cfg = config.nomarchy.services; cfg = config.nomarchy.services;
# Control Center's Printing toggle writes settings.printing.enable; read it # Menu Preferences Printing 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
# state-read.nix rather than a raw stack. null = key absent, which # state-read.nix rather than a raw stack. null = key absent, which
@@ -88,7 +88,7 @@ in
`services.ollama.acceleration` natively for GPU offload''; `services.ollama.acceleration` natively for GPU offload'';
# default stays a plain `false` here; the state bridge is a mkDefault # default stays a plain `false` here; the state bridge is a mkDefault
# below, from settings.printing.enable (Control Center's Printing # below, from settings.printing.enable (menu Preferences Printing
# toggle) — see ROADMAP § state bridges (#116) for why the old read of # toggle) — see ROADMAP § state bridges (#116) for why the old read of
# `config.nomarchy.settings` never worked. # `config.nomarchy.settings` never worked.
printing.enable = lib.mkEnableOption '' printing.enable = lib.mkEnableOption ''

View File

@@ -1,7 +0,0 @@
{ writeShellApplication, coreutils, gawk, jq, gum }:
writeShellApplication {
name = "nomarchy-control-center";
runtimeInputs = [ coreutils gawk jq gum ];
text = builtins.readFile ./nomarchy-control-center.sh;
}

View File

@@ -1,246 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Nomarchy Control Center — TUI frontend over nomarchy-state-sync and tools
# Uses charmbracelet/gum for rendering.
trap 'exit 0' SIGINT
function get_state() {
nomarchy-state-sync get "$1" 2>/dev/null || echo ""
}
function set_state() {
nomarchy-state-sync --quiet set "$1" "$2" --no-switch
}
function first_boot() {
gum style --border normal --margin "1" --padding "1 2" --border-foreground 212 "Welcome to Nomarchy!"
echo "Pick a Theme Preset:"
themes=$(nomarchy-state-sync list)
chosen_theme=$(printf "%s" "$themes" | gum choose)
if [ -n "$chosen_theme" ]; then
echo "Applying theme $chosen_theme..."
nomarchy-state-sync apply "$chosen_theme"
fi
if gum confirm "Enable auto-commit for your config? (settings changes + a sweep of pending edits before each rebuild)"; then
set_state "settings.autoCommit" "true"
else
set_state "settings.autoCommit" "false"
fi
if command -v nomarchy-autotimezone >/dev/null 2>&1; then
if gum confirm "Enable automatic timezone detection?"; then
set_state "settings.autoTimezone" "true"
else
set_state "settings.autoTimezone" "false"
fi
fi
# Post-install hardware/day-2 tips (#43 Firmware; #73 fingerprint +
# doctor). Self-gated on the same CLI presence the System menu uses;
# first-boot is once, so no dismiss state file is needed.
if command -v fwupdmgr >/dev/null 2>&1; then
gum style --foreground 245 \
"Tip: SUPER+M → System Firmware checks LVFS updates (never auto-flashes)."
fi
if command -v fprintd-list >/dev/null 2>&1; then
gum style --foreground 245 \
"Tip: SUPER+M → System Fingerprint to enroll a finger (or fprintd-enroll)."
fi
if command -v nomarchy-doctor >/dev/null 2>&1; then
gum style --foreground 245 \
"Tip: run nomarchy-doctor (or SUPER+M → System Doctor) for a read-only health check."
fi
gum style --foreground 212 "First boot configuration complete!"
echo "You can always change these later in the Control Center."
read -r -n 1 -s -p "Press any key to exit..."
echo
}
function main_menu() {
while true; do
choice=$(gum choose "Appearance" "System Toggles" "Health (Doctor)" "Rollback" "Exit")
case "$choice" in
"Appearance") appearance_menu ;;
"System Toggles") toggles_menu ;;
"Health (Doctor)")
if command -v nomarchy-doctor >/dev/null 2>&1; then
nomarchy-doctor || true
else
echo "nomarchy-doctor not found."
fi
echo
read -r -n 1 -s -p "Press any key to return..."
echo
;;
"Rollback")
gens=$(home-manager generations 2>/dev/null | head -10)
if [ -z "$gens" ]; then
echo "No Home Manager generations found."
else
tmp=$(mktemp)
printf "%s\n" "$gens" | awk '{ printf "Desktop gen %s — %s %s%s\n", $5, $1, $2, (NR==1 ? " (current)" : "") }' > "$tmp"
sel=$(gum choose < "$tmp" || true)
rm -f "$tmp"
if [ -n "$sel" ]; then
num=${sel#Desktop gen }
num=${num%% *}
path=$(printf "%s\n" "$gens" | awk -v n="$num" '$5 == n { print $7 }')
if [ -x "$path/activate" ]; then
echo "Activating Home Manager generation $num..."
"$path/activate"
echo "Desktop rolled back to generation $num."
else
echo "Generation $num not found."
fi
fi
fi
echo
read -r -n 1 -s -p "Press any key to return..."
echo
;;
"Exit"|*) break ;;
esac
done
}
function appearance_menu() {
while true; do
choice=$(gum choose "Pick Theme" "Toggle Blur" "Set Gaps" "Back")
case "$choice" in
"Pick Theme")
themes=$(nomarchy-state-sync list)
chosen=$(printf "%s" "$themes" | gum choose)
if [ -n "$chosen" ]; then
nomarchy-state-sync apply "$chosen"
fi
;;
"Toggle Blur")
blur=$(get_state "ui.blur")
if [ "$blur" = "true" ]; then
set_state "ui.blur" "false"
else
set_state "ui.blur" "true"
fi
echo "Blur setting saved (requires rebuild)."
sleep 1
;;
"Set Gaps")
gaps=$(gum input --prompt "Enter gaps in pixels: " --placeholder "$(get_state ui.gapsOut)")
if [ -n "$gaps" ]; then
set_state "ui.gapsOut" "$gaps"
echo "Gaps set to $gaps (requires rebuild)."
sleep 1
fi
;;
"Back"|*) break ;;
esac
done
}
function toggles_menu() {
while true; do
choice=$(gum choose "Auto-commit" "Auto-timezone" "Night Light" "Updates" "Bluetooth" "Printing" "Terminal" "Keyboard Layout" "Auto-Login" "Back")
case "$choice" in
"Auto-commit")
cur=$(get_state "settings.autoCommit")
if [ "$cur" = "true" ]; then
set_state "settings.autoCommit" "false"
echo "Auto-commit disabled."
else
set_state "settings.autoCommit" "true"
echo "Auto-commit enabled."
fi
sleep 1
;;
"Auto-timezone")
cur=$(get_state "settings.autoTimezone")
if [ "$cur" = "true" ]; then
set_state "settings.autoTimezone" "false"
echo "Auto-timezone disabled."
else
set_state "settings.autoTimezone" "true"
echo "Auto-timezone enabled."
fi
sleep 1
;;
"Night Light")
if command -v nomarchy-nightlight >/dev/null 2>&1; then
nomarchy-nightlight toggle
sleep 1
else
echo "nomarchy-nightlight not found."
sleep 1
fi
;;
"Updates")
cur=$(get_state "settings.updates.enable")
if [ "$cur" = "true" ]; then
set_state "settings.updates.enable" "false"
echo "Updates checker disabled (requires rebuild)."
else
set_state "settings.updates.enable" "true"
echo "Updates checker enabled (requires rebuild)."
fi
sleep 1
;;
"Bluetooth")
cur=$(get_state "settings.bluetooth.enable")
if [ "$cur" = "true" ]; then
set_state "settings.bluetooth.enable" "false"
echo "Bluetooth disabled (requires rebuild)."
else
set_state "settings.bluetooth.enable" "true"
echo "Bluetooth enabled (requires rebuild)."
fi
sleep 1
;;
"Printing")
cur=$(get_state "settings.printing.enable")
if [ "$cur" = "true" ]; then
set_state "settings.printing.enable" "false"
echo "Printing services disabled (requires rebuild)."
else
set_state "settings.printing.enable" "true"
echo "Printing services enabled (requires rebuild)."
fi
sleep 1
;;
"Terminal")
echo "Kitty is Nomarchy's terminal (themed from theme-state)."
echo "Override nomarchy.terminal in home.nix only if you install another yourself."
sleep 2
;;
"Keyboard Layout")
layout=$(gum input --prompt "Enter keyboard layout (e.g. us, de, es): " --placeholder "$(get_state settings.keyboard.layout | tr -d '"')")
if [ -n "$layout" ]; then
set_state "settings.keyboard.layout" "\"$layout\""
echo "Keyboard layout set to $layout (requires rebuild)."
fi
sleep 1
;;
"Auto-Login")
user=$(gum input --prompt "Enter auto-login username (or empty to disable): " --placeholder "$(get_state settings.greeter.autoLogin | tr -d '"')")
if [ -n "$user" ]; then
set_state "settings.greeter.autoLogin" "\"$user\""
echo "Auto-login set to $user (requires rebuild)."
else
set_state "settings.greeter.autoLogin" "null"
echo "Auto-login disabled (requires rebuild)."
fi
sleep 1
;;
"Back"|*) break ;;
esac
done
}
if [ "${1:-}" = "--first-boot" ]; then
first_boot
else
main_menu
fi