feat(core): migrate system state to unified declarative JSON

- Consolidate all configuration toggles (suspend, idle, waybar, etc.) into ~/.config/home-manager/state.json
- Introduce nomarchy.toggles and nomarchy.hyprland options in Nix
- Inject toggle states into all bin/ scripts via environment variables
- Update toggle scripts to mutate JSON and trigger background rebuilds
- Add a migration script to transition legacy flag files to the new format
This commit is contained in:
Bernardo Magri
2026-04-04 10:11:09 +01:00
parent f1ed0d7f47
commit cfd5e4bb65
23 changed files with 378 additions and 173 deletions

View File

@@ -2,11 +2,6 @@
let
palettes = import ../../themes/nomarchy-palettes.nix;
# Reads the state file. If it doesn't exist, defaults to nord.
stateFile = "${config.home.homeDirectory}/.config/home-manager/theme-state.nix";
activeThemeName = if builtins.pathExists stateFile then
lib.removeSuffix "\n" (builtins.readFile stateFile)
else "nord";
userPackagesFile = "${config.home.homeDirectory}/.config/home-manager/user-packages.json";
userPackages = if builtins.pathExists userPackagesFile then
let
@@ -20,6 +15,9 @@ in
imports = [
inputs.nix-colors.homeManagerModules.default
inputs.walker.homeManagerModules.default
./options.nix
./state.nix
./theme-files.nix
./fonts.nix
./alacritty.nix
./nightlight.nix
@@ -34,7 +32,7 @@ in
./swayosd.nix
];
colorScheme = palettes.${activeThemeName} or palettes.nord;
colorScheme = palettes.${config.nomarchy.theme} or palettes.nord;
home.packages = with pkgs; [
firefox

View File

@@ -1,22 +1,6 @@
{ config, pkgs, lib, ... }:
{ ... }:
let
# Standardize state files to a common directory
stateDir = "${config.home.homeDirectory}/.config/home-manager";
fontStateFile = "${stateDir}/font-state.nix";
activeFont = if builtins.pathExists fontStateFile then
lib.removeSuffix "\n" (builtins.readFile fontStateFile)
else "JetBrainsMono Nerd Font";
in
{
options.nomarchy.fonts = {
monospace = lib.mkOption {
type = lib.types.str;
default = activeFont;
description = "Monospace font for the system";
};
};
config = {
fonts.fontconfig.enable = true;
xdg.dataFile."fonts/nomarchy.ttf".source = ../../config/nomarchy.ttf;

View File

@@ -1,14 +1,8 @@
{ config, pkgs, lib, ... }:
let
wallpaperStateFile = "${config.home.homeDirectory}/.config/home-manager/wallpaper-state.nix";
activeWallpaper = if builtins.pathExists wallpaperStateFile then
lib.removeSuffix "\n" (builtins.readFile wallpaperStateFile)
else "";
hyprlandStateFile = "${config.home.homeDirectory}/.config/home-manager/hyprland-state.json";
hyprlandState = if builtins.pathExists hyprlandStateFile then
builtins.fromJSON (builtins.readFile hyprlandStateFile)
else { gaps_out = 10; gaps_in = 5; border_size = 2; };
activeWallpaper = config.nomarchy.wallpaper;
hyprlandState = config.nomarchy.hyprland;
in
{
wayland.windowManager.hyprland = {
@@ -23,10 +17,9 @@ in
};
"exec-once" = [
"swww-daemon & sleep 0.5 && swww img ${activeWallpaper} --transition-type none"
"waybar"
"nomarchy-on-boot"
"nomarchy-welcome"
];
] ++ (lib.optional config.nomarchy.toggles.waybar "waybar");
"bind" = [
"SUPER, Space, exec, walker"
"SUPER ALT, Space, exec, nomarchy-theme-selector"

View File

@@ -1,13 +1,7 @@
{ config, pkgs, ... }:
{ config, ... }:
let
stateFile = "${config.home.homeDirectory}/.config/home-manager/idle-state.json";
idleState = if builtins.pathExists stateFile then
builtins.fromJSON (builtins.readFile stateFile)
else { enabled = true; };
in
{
services.hypridle = {
enable = idleState.enabled;
enable = config.nomarchy.toggles.idle;
};
}

View File

@@ -1,11 +1,7 @@
{ config, pkgs, ... }:
{ config, ... }:
let
stateFile = "${config.home.homeDirectory}/.config/home-manager/hyprsunset-state.json";
hyprsunsetState = if builtins.pathExists stateFile then
builtins.fromJSON (builtins.readFile stateFile)
else { enabled = false; temperature = 4000; };
temp = toString (if hyprsunsetState.enabled then hyprsunsetState.temperature else 6500);
temp = toString (if config.nomarchy.toggles.nightlight then config.nomarchy.nightlightTemperature else 6500);
in
{
services.hyprsunset = {

77
modules/home/options.nix Normal file
View File

@@ -0,0 +1,77 @@
{ lib, ... }:
{
options.nomarchy = {
toggles = {
suspend = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to show suspend in system menu.";
};
screensaver = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether the screensaver is enabled.";
};
idle = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether the idle lock is enabled.";
};
nightlight = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether the nightlight is enabled.";
};
waybar = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether the top bar is enabled.";
};
skipVsCodeTheme = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to skip theme changes in VSCode.";
};
};
nightlightTemperature = lib.mkOption {
type = lib.types.int;
default = 4000;
description = "Temperature for the nightlight.";
};
theme = lib.mkOption {
type = lib.types.str;
default = "nord";
description = "System theme name.";
};
wallpaper = lib.mkOption {
type = lib.types.str;
default = "";
description = "System wallpaper path.";
};
hyprland = {
gaps_in = lib.mkOption {
type = lib.types.int;
default = 5;
description = "Inner gaps for Hyprland.";
};
gaps_out = lib.mkOption {
type = lib.types.int;
default = 10;
description = "Outer gaps for Hyprland.";
};
border_size = lib.mkOption {
type = lib.types.int;
default = 2;
description = "Border size for Hyprland.";
};
};
fonts = {
monospace = lib.mkOption {
type = lib.types.str;
default = "JetBrainsMono Nerd Font";
description = "System monospace font.";
};
};
};
}

View File

@@ -42,11 +42,18 @@ let
cp -r * $out/bin/
chmod +x $out/bin/*
# Wrap every script to ensure dependencies are in PATH
# Wrap every script to ensure dependencies are in PATH and inject configuration
for file in $out/bin/*; do
if [ -f "$file" ]; then
wrapProgram "$file" \
--prefix PATH : ${lib.makeBinPath nomarchy-deps}
--prefix PATH : ${lib.makeBinPath nomarchy-deps} \
--set NOMARCHY_TOGGLE_SUSPEND "${if config.nomarchy.toggles.suspend then "true" else "false"}" \
--set NOMARCHY_TOGGLE_SCREENSAVER "${if config.nomarchy.toggles.screensaver then "true" else "false"}" \
--set NOMARCHY_TOGGLE_IDLE "${if config.nomarchy.toggles.idle then "true" else "false"}" \
--set NOMARCHY_TOGGLE_NIGHTLIGHT "${if config.nomarchy.toggles.nightlight then "true" else "false"}" \
--set NOMARCHY_TOGGLE_WAYBAR "${if config.nomarchy.toggles.waybar then "true" else "false"}" \
--set NOMARCHY_TOGGLE_SKIP_VSCODE_THEME "${if config.nomarchy.toggles.skipVsCodeTheme then "true" else "false"}" \
--set NOMARCHY_MONOSPACE_FONT "${config.nomarchy.fonts.monospace}"
fi
done
'';

43
modules/home/state.nix Normal file
View File

@@ -0,0 +1,43 @@
{ config, lib, ... }:
let
stateDir = "${config.home.homeDirectory}/.config/home-manager";
# Helper to read state from a file, with a default
readState = file: default:
if builtins.pathExists "${stateDir}/${file}" then
let
content = builtins.readFile "${stateDir}/${file}";
cleanContent = lib.removeSuffix "\n" content;
in
if lib.hasSuffix ".json" file then
builtins.fromJSON cleanContent
else
cleanContent
else
default;
# Unified state reading
togglesState = readState "state.json" {};
in
{
config.nomarchy = {
toggles = {
suspend = togglesState.suspend or true;
screensaver = togglesState.screensaver or true;
idle = togglesState.idle or true;
nightlight = togglesState.nightlight or false;
waybar = togglesState.waybar or true;
skipVsCodeTheme = togglesState.skipVsCodeTheme or false;
};
nightlightTemperature = togglesState.nightlightTemperature or 4000;
theme = togglesState.theme or "nord";
wallpaper = togglesState.wallpaper or "";
hyprland = {
gaps_in = togglesState.hyprland.gaps_in or 5;
gaps_out = togglesState.hyprland.gaps_out or 10;
border_size = togglesState.hyprland.border_size or 2;
};
fonts.monospace = togglesState.font or "JetBrainsMono Nerd Font";
};
}

View File

@@ -4,14 +4,9 @@ let
# Re-use our state-based logic
palettes = import ../../themes/nomarchy-palettes.nix;
themeStateFile = "${config.home.homeDirectory}/.config/home-manager/theme-state.nix";
activeThemeName = if builtins.pathExists themeStateFile then
lib.removeSuffix "\n" (builtins.readFile themeStateFile)
else "nord";
wallpaperStateFile = "${config.home.homeDirectory}/.config/home-manager/wallpaper-state.nix";
activeWallpaper = if builtins.pathExists wallpaperStateFile then
lib.removeSuffix "\n" (builtins.readFile wallpaperStateFile)
activeThemeName = config.nomarchy.theme;
activeWallpaper = if config.nomarchy.wallpaper != "" then
config.nomarchy.wallpaper
else "${../../themes/catppuccin/backgrounds/1-totoro.png}"; # Fallback
# Map nix-colors palette to a format Stylix expects (attrset of hex strings)

View File

@@ -6,55 +6,32 @@ let
themeList = builtins.concatStringsSep "\\n" themeNames;
nomarchy-theme-selector = pkgs.writeShellScriptBin "nomarchy-theme-selector" ''
STATE_DIR="$HOME/.config/home-manager"
THEME_STATE_FILE="$STATE_DIR/theme-state.nix"
WALLPAPER_STATE_FILE="$STATE_DIR/wallpaper-state.nix"
if [ -d "/etc/nixos/nomarchy/themes" ]; then
THEMES_DIR="/etc/nixos/nomarchy/themes"
elif [ -d "/etc/nomarchy/themes" ]; then
THEMES_DIR="/etc/nomarchy/themes"
else
THEMES_DIR="/etc/nixos/themes"
fi # This depends on where the repo is cloned
mkdir -p "$STATE_DIR"
SELECTED_THEME=$(echo -e "${themeList}" | walker --dmenu)
if [ -n "$SELECTED_THEME" ]; then
echo "$SELECTED_THEME" > "$THEME_STATE_FILE"
# Try to find a background for this theme
BG_DIR="$THEMES_DIR/$SELECTED_THEME/backgrounds"
if [ -d "$BG_DIR" ]; then
BG=$(ls "$BG_DIR" | head -n 1)
if [ -n "$BG" ]; then
echo "$BG_DIR/$BG" > "$WALLPAPER_STATE_FILE"
swww img "$BG_DIR/$BG" --transition-type outer --transition-pos 0.85,0.97 --transition-step 90 &
fi
fi
env-update
nomarchy-theme-set "$SELECTED_THEME"
fi
'';
nomarchy-font-selector = pkgs.writeShellScriptBin "nomarchy-font-selector" ''
STATE_DIR="$HOME/.config/home-manager"
STATE_FILE="$STATE_DIR/font-state.nix"
STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR"
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
# Simple list of common nerd fonts, could be expanded
FONTS="JetBrainsMono Nerd Font\nRobotoMono Nerd Font\nFiraCode Nerd Font\nUbuntuMono Nerd Font"
SELECTED_FONT=$(echo -e "$FONTS" | walker --dmenu)
if [ -n "$SELECTED_FONT" ]; then
echo "$SELECTED_FONT" > "$STATE_FILE"
TMP_JSON=$(mktemp)
jq ".font = \"$SELECTED_FONT\"" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
env-update
fi
'';
nomarchy-wallpaper-selector = pkgs.writeShellScriptBin "nomarchy-wallpaper-selector" ''
STATE_DIR="$HOME/.config/home-manager"
WALLPAPER_STATE_FILE="$STATE_DIR/wallpaper-state.nix"
STATE_FILE="$STATE_DIR/state.json"
if [ -d "/etc/nixos/nomarchy/themes" ]; then
THEMES_DIR="/etc/nixos/nomarchy/themes"
elif [ -d "/etc/nomarchy/themes" ]; then
@@ -64,13 +41,15 @@ let
fi
mkdir -p "$STATE_DIR"
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
# List all images in all themes backgrounds
WALLPAPERS=$(find "$THEMES_DIR" -type f \( -name "*.jpg" -o -name "*.png" \))
SELECTED_WP=$(echo -e "$WALLPAPERS" | walker --dmenu)
if [ -n "$SELECTED_WP" ]; then
echo "$SELECTED_WP" > "$WALLPAPER_STATE_FILE"
TMP_JSON=$(mktemp)
jq ".wallpaper = \"$SELECTED_WP\"" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
swww img "$SELECTED_WP" --transition-type outer --transition-pos 0.85,0.97 --transition-step 90 &
env-update
fi