refactor: major architectural restructure for theme-centric organization

Theme System:
- Move all theme app configs to apps/ subdirectory (20 themes)
- Add theme-loader.nix for dynamic theme config deployment
- Simplify stylix.nix to focus on base theming only

Override System:
- Add overrides.nix for file-based config overrides
- Add behavior-configs.nix for non-visual configuration
- Split hypr/nomarchy.conf into behavior vs visual sections

Module Improvements:
- Add lib.mkDefault to all customizable settings
- Add modules/lib/ with shared utilities and state schema
- Update all home and system modules for downstream overridability

Installer:
- New minimal TTY installer (installer/install.sh)
- Golden path: BTRFS + LUKS2 (disko-golden.nix)
- New installer-iso.nix for TTY-only installation
- Keep graphical installer as installerIsoGraphical option

Cleanup:
- Remove obsolete install.sh, disko-ext4.nix, install-nomarchy.sh
- Update live-iso.nix references
- Add .claude/ to .gitignore for local IDE settings

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-04-11 19:38:27 +01:00
parent 769fd88f25
commit b27fc5aee8
141 changed files with 2014 additions and 943 deletions

2
.gitignore vendored
View File

@@ -6,6 +6,8 @@ result-*
# Ignore automatically generated direnv output # Ignore automatically generated direnv output
.direnv .direnv
# Local IDE settings
.claude/
# VM and ISO artifacts # VM and ISO artifacts
*.qcow2 *.qcow2

View File

@@ -10,7 +10,7 @@ if [[ -z $font_name ]]; then
exit 1 exit 1
fi fi
STATE_DIR="$HOME/.config/home-manager" STATE_DIR="$HOME/.config/nomarchy"
STATE_FILE="$STATE_DIR/state.json" STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR" mkdir -p "$STATE_DIR"
@@ -18,19 +18,19 @@ mkdir -p "$STATE_DIR"
if fc-list | grep -iq "$font_name"; then if fc-list | grep -iq "$font_name"; then
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".font = \"$font_name\"" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --arg font "$font_name" '.font = $font' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Font set to $font_name declaratively. Applying changes..." echo "Font set to $font_name declaratively. Applying changes..."
env-update env-update
# Instant feedback for certain apps via IPC # Instant feedback for certain apps via IPC
if pgrep -x kitty; then if pgrep -x kitty; then
pkill -USR1 kitty pkill -USR1 kitty
fi fi
if pgrep -x ghostty; then if pgrep -x ghostty; then
pkill -SIGUSR2 ghostty pkill -SIGUSR2 ghostty
notify-send -u low " You must restart Ghostty to see font change" notify-send -u low " You must restart Ghostty to see font change"
fi fi
nomarchy-hook font-set "$font_name" nomarchy-hook font-set "$font_name"
else else
echo "Font '$font_name' not found." echo "Font '$font_name' not found."

View File

@@ -3,7 +3,7 @@
# Cycles through the background images available for the current theme. # Cycles through the background images available for the current theme.
# Declarative + Hybrid (instant swww) for Nomarchy NixOS. # Declarative + Hybrid (instant swww) for Nomarchy NixOS.
STATE_DIR="$HOME/.config/home-manager" STATE_DIR="$HOME/.config/nomarchy"
STATE_FILE="$STATE_DIR/state.json" STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR" mkdir -p "$STATE_DIR"
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE" [[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
@@ -45,7 +45,7 @@ NEXT_INDEX=$(((INDEX + 1) % TOTAL))
NEW_BG="$BG_DIR/${BACKGROUNDS[$NEXT_INDEX]}" NEW_BG="$BG_DIR/${BACKGROUNDS[$NEXT_INDEX]}"
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".wallpaper = \"$NEW_BG\"" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --arg wp "$NEW_BG" '.wallpaper = $wp' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
# Instant feedback via swww # Instant feedback via swww
if pgrep -x swww-daemon >/dev/null; then if pgrep -x swww-daemon >/dev/null; then

View File

@@ -10,7 +10,7 @@ if [[ -z $THEME_NAME ]]; then
exit 1 exit 1
fi fi
STATE_DIR="$HOME/.config/home-manager" STATE_DIR="$HOME/.config/nomarchy"
STATE_FILE="$STATE_DIR/state.json" STATE_FILE="$STATE_DIR/state.json"
# Resolve themes directory (Built-in from Nix store via Home Manager, or user extra) # Resolve themes directory (Built-in from Nix store via Home Manager, or user extra)
@@ -25,17 +25,15 @@ mkdir -p "$STATE_DIR"
if [ ! -d "$THEMES_DIR/$THEME_NAME" ] && ! [[ "$THEME_NAME" == "nord" ]]; then if [ ! -d "$THEMES_DIR/$THEME_NAME" ] && ! [[ "$THEME_NAME" == "nord" ]]; then
echo "Theme '$THEME_NAME' not found in $THEMES_DIR" echo "Theme '$THEME_NAME' not found in $THEMES_DIR"
# Check if it exists in the palettes file
# (Assuming nomarchy-palettes.nix is imported in Nix)
fi fi
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".theme = \"$THEME_NAME\"" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --arg theme "$THEME_NAME" '.theme = $theme' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
# Sync to system state if we have permissions (for system-level theming like browser policies) # Sync to system state if we have permissions (for system-level theming like browser policies)
SYSTEM_STATE_FILE="/etc/nixos/state.json" SYSTEM_STATE_FILE="/etc/nixos/state.json"
if [ -w "$SYSTEM_STATE_FILE" ] || [ -w "/etc/nixos" ]; then if [ -w "$SYSTEM_STATE_FILE" ] || [ -w "/etc/nixos" ]; then
sudo jq ".theme = \"$THEME_NAME\"" "$SYSTEM_STATE_FILE" > /tmp/system-state.json 2>/dev/null && sudo mv /tmp/system-state.json "$SYSTEM_STATE_FILE" 2>/dev/null || true sudo jq --arg theme "$THEME_NAME" '.theme = $theme' "$SYSTEM_STATE_FILE" > /tmp/system-state.json 2>/dev/null && sudo mv /tmp/system-state.json "$SYSTEM_STATE_FILE" 2>/dev/null || true
fi fi
# Try to find a background for this theme # Try to find a background for this theme
@@ -44,7 +42,7 @@ if [ -d "$BG_DIR" ]; then
BG=$(ls "$BG_DIR" | head -n 1) BG=$(ls "$BG_DIR" | head -n 1)
if [ -n "$BG" ]; then if [ -n "$BG" ]; then
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".wallpaper = \"$BG_DIR/$BG\"" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --arg wp "$BG_DIR/$BG" '.wallpaper = $wp' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
fi fi
fi fi

View File

@@ -4,7 +4,7 @@
# This script only updates the global state.json. # This script only updates the global state.json.
# Home Manager (modules/home/vscode.nix) handles the declarative settings injection. # Home Manager (modules/home/vscode.nix) handles the declarative settings injection.
STATE_DIR="$HOME/.config/home-manager" STATE_DIR="$HOME/.config/nomarchy"
STATE_FILE="$STATE_DIR/state.json" STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR" mkdir -p "$STATE_DIR"

View File

@@ -3,23 +3,24 @@
# Toggles the nightlight (hyprsunset). # Toggles the nightlight (hyprsunset).
# Hybrid: updates state.json and provides instant feedback. # Hybrid: updates state.json and provides instant feedback.
STATE_FILE="$HOME/.config/home-manager/state.json" STATE_DIR="$HOME/.config/nomarchy"
mkdir -p "$(dirname "$STATE_FILE")" STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR"
# Initialize if doesn't exist # Initialize if doesn't exist
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE" [[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
if [[ $NNOMARCHY_TOGGLE_NIGHTLIGHT == "false" ]]; then if [[ $NOMARCHY_TOGGLE_NIGHTLIGHT == "false" ]]; then
NEW_VALUE="true" NEW_VALUE="true"
hyprctl dispatch exec hyprsunset --temperature 4000 hyprctl dispatch exec hyprsunset --temperature 4000
notify-send -u low "󰔎 Nightlight enabled" notify-send -u low " Nightlight enabled"
else else
NEW_VALUE="false" NEW_VALUE="false"
pkill hyprsunset pkill hyprsunset
notify-send -u low "󰔎 Nightlight disabled" notify-send -u low " Nightlight disabled"
fi fi
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".nightlight = $NEW_VALUE" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --argjson val "$NEW_VALUE" '.nightlight = $val' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Nightlight state set to $NEW_VALUE. Environment will be fully updated on next rebuild." echo "Nightlight state set to $NEW_VALUE. Environment will be fully updated on next rebuild."

View File

@@ -8,7 +8,7 @@ STATE_FILE="/etc/nixos/state.json"
# Check if supergfxd is enabled in config # Check if supergfxd is enabled in config
if [[ $(sudo jq -r '.features.hybridGPU // false' "$STATE_FILE") != "true" ]]; then if [[ $(sudo jq -r '.features.hybridGPU // false' "$STATE_FILE") != "true" ]]; then
if gum confirm "Hybrid GPU support is not enabled. Enable it now? (Requires sys-update)"; then if gum confirm "Hybrid GPU support is not enabled. Enable it now? (Requires sys-update)"; then
sudo jq ".features.hybridGPU = true" "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE" sudo jq '.features.hybridGPU = true' "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE"
echo "Hybrid GPU support enabled in configuration. Applying changes..." echo "Hybrid GPU support enabled in configuration. Applying changes..."
sudo sys-update sudo sys-update
echo "Please run this command again after the update." echo "Please run this command again after the update."

View File

@@ -11,7 +11,7 @@ off) value="false" ;;
*) echo "Usage: nomarchy-wifi-powersave <on|off>"; exit 1 ;; *) echo "Usage: nomarchy-wifi-powersave <on|off>"; exit 1 ;;
esac esac
sudo jq ".wifi.powersave = $value" "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE" sudo jq --argjson val "$value" '.wifi.powersave = $val' "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE"
echo "Wifi powersave set to $1. Applying changes..." echo "Wifi powersave set to $1. Applying changes..."
sudo sys-update sudo sys-update

View File

@@ -14,13 +14,13 @@ if [ ! -f "$STATE_FILE" ]; then
echo "[]" > "$STATE_FILE" echo "[]" > "$STATE_FILE"
fi fi
if jq -e ". | index(\"$PKG_NAME\")" "$STATE_FILE" >/dev/null; then if jq -e --arg pkg "$PKG_NAME" '. | index($pkg)' "$STATE_FILE" >/dev/null; then
echo "Package $PKG_NAME is already in your user-packages.json" echo "Package $PKG_NAME is already in your user-packages.json"
exit 0 exit 0
fi fi
# Append package to the JSON array # Append package to the JSON array safely
jq ". + [\"$PKG_NAME\"]" "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE" jq --arg pkg "$PKG_NAME" '. + [$pkg]' "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
echo "Package $PKG_NAME added declaratively to $STATE_FILE." echo "Package $PKG_NAME added declaratively to $STATE_FILE."
echo "Applying changes with env-update..." echo "Applying changes with env-update..."

View File

@@ -14,13 +14,13 @@ if [ ! -f "$STATE_FILE" ]; then
exit 0 exit 0
fi fi
if ! jq -e ". | index(\"$PKG_NAME\")" "$STATE_FILE" >/dev/null; then if ! jq -e --arg pkg "$PKG_NAME" '. | index($pkg)' "$STATE_FILE" >/dev/null; then
echo "Package $PKG_NAME is not in your user-packages.json" echo "Package $PKG_NAME is not in your user-packages.json"
exit 0 exit 0
fi fi
# Remove package from the JSON array # Remove package from the JSON array safely
jq ". - [\"$PKG_NAME\"]" "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE" jq --arg pkg "$PKG_NAME" '. - [$pkg]' "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
echo "Package $PKG_NAME removed declaratively from $STATE_FILE." echo "Package $PKG_NAME removed declaratively from $STATE_FILE."
echo "Applying changes with env-update..." echo "Applying changes with env-update..."

View File

@@ -2,22 +2,32 @@
# Nomarchy Pre-flight State Migration # Nomarchy Pre-flight State Migration
# Migrates legacy state files into the unified state.json before Nix evaluation # Migrates legacy state files into the unified state.json before Nix evaluation
STATE_DIR="$HOME/.config/home-manager" STATE_DIR="$HOME/.config/nomarchy"
OLD_STATE_DIR="$HOME/.config/home-manager"
OLD_TOGGLES_DIR="$HOME/.local/state/nomarchy/toggles" OLD_TOGGLES_DIR="$HOME/.local/state/nomarchy/toggles"
IDLE_STATE_FILE="$STATE_DIR/idle-state.json" IDLE_STATE_FILE="$OLD_STATE_DIR/idle-state.json"
NIGHTLIGHT_STATE_FILE="$STATE_DIR/hyprsunset-state.json" NIGHTLIGHT_STATE_FILE="$OLD_STATE_DIR/hyprsunset-state.json"
HYPRLAND_STATE_FILE="$STATE_DIR/hyprland-state.json" HYPRLAND_STATE_FILE="$OLD_STATE_DIR/hyprland-state.json"
THEME_STATE_FILE="$STATE_DIR/theme-state.nix" THEME_STATE_FILE="$OLD_STATE_DIR/theme-state.nix"
WALLPAPER_STATE_FILE="$STATE_DIR/wallpaper-state.nix" WALLPAPER_STATE_FILE="$OLD_STATE_DIR/wallpaper-state.nix"
FONT_STATE_FILE="$STATE_DIR/font-state.nix" FONT_STATE_FILE="$OLD_STATE_DIR/font-state.nix"
OLD_STATE_FILE="$OLD_STATE_DIR/state.json"
NEW_STATE_FILE="$STATE_DIR/state.json" NEW_STATE_FILE="$STATE_DIR/state.json"
# We expect jq to be in PATH (it's a dependency of nomarchy-scripts) # We expect jq to be in PATH (it's a dependency of nomarchy-scripts)
JQ="jq" JQ="jq"
mkdir -p "$(dirname "$NEW_STATE_FILE")" mkdir -p "$STATE_DIR"
[[ ! -f $NEW_STATE_FILE ]] && echo "{}" > "$NEW_STATE_FILE" [[ ! -f $NEW_STATE_FILE ]] && echo "{}" > "$NEW_STATE_FILE"
# 0. Migrate from old home-manager state.json location
if [[ -f "$OLD_STATE_FILE" ]] && [[ "$OLD_STATE_FILE" != "$NEW_STATE_FILE" ]]; then
# Merge old state into new state
TMP_FILE=$(mktemp)
$JQ -s '.[0] * .[1]' "$OLD_STATE_FILE" "$NEW_STATE_FILE" > "$TMP_FILE" && mv "$TMP_FILE" "$NEW_STATE_FILE"
rm "$OLD_STATE_FILE" 2>/dev/null || true
fi
# 1. Migrate .local/state/nomarchy/toggles # 1. Migrate .local/state/nomarchy/toggles
if [[ -d $OLD_TOGGLES_DIR ]]; then if [[ -d $OLD_TOGGLES_DIR ]]; then
for file in "$OLD_TOGGLES_DIR"/*; do for file in "$OLD_TOGGLES_DIR"/*; do
@@ -43,7 +53,7 @@ fi
if [[ -f $IDLE_STATE_FILE ]]; then if [[ -f $IDLE_STATE_FILE ]]; then
ENABLED=$($JQ '.enabled' "$IDLE_STATE_FILE") ENABLED=$($JQ '.enabled' "$IDLE_STATE_FILE")
if [[ "$ENABLED" == "true" || "$ENABLED" == "false" ]]; then if [[ "$ENABLED" == "true" || "$ENABLED" == "false" ]]; then
$JQ ".idle = $ENABLED" "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE" $JQ --argjson val "$ENABLED" '.idle = $val' "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE"
fi fi
rm "$IDLE_STATE_FILE" rm "$IDLE_STATE_FILE"
fi fi
@@ -51,7 +61,7 @@ fi
if [[ -f $NIGHTLIGHT_STATE_FILE ]]; then if [[ -f $NIGHTLIGHT_STATE_FILE ]]; then
ENABLED=$($JQ '.enabled' "$NIGHTLIGHT_STATE_FILE") ENABLED=$($JQ '.enabled' "$NIGHTLIGHT_STATE_FILE")
TEMP=$($JQ '.temperature' "$NIGHTLIGHT_STATE_FILE") TEMP=$($JQ '.temperature' "$NIGHTLIGHT_STATE_FILE")
$JQ ".nightlight = $ENABLED | .nightlightTemperature = $TEMP" "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE" $JQ --argjson enabled "$ENABLED" --argjson temp "$TEMP" '.nightlight = $enabled | .nightlightTemperature = $temp' "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE"
rm "$NIGHTLIGHT_STATE_FILE" rm "$NIGHTLIGHT_STATE_FILE"
fi fi
@@ -59,25 +69,25 @@ if [[ -f $HYPRLAND_STATE_FILE ]]; then
GAPS_OUT=$($JQ '.gaps_out' "$HYPRLAND_STATE_FILE") GAPS_OUT=$($JQ '.gaps_out' "$HYPRLAND_STATE_FILE")
GAPS_IN=$($JQ '.gaps_in' "$HYPRLAND_STATE_FILE") GAPS_IN=$($JQ '.gaps_in' "$HYPRLAND_STATE_FILE")
BORDER_SIZE=$($JQ '.border_size' "$HYPRLAND_STATE_FILE") BORDER_SIZE=$($JQ '.border_size' "$HYPRLAND_STATE_FILE")
$JQ ".hyprland = {\"gaps_out\": $GAPS_OUT, \"gaps_in\": $GAPS_IN, \"border_size\": $BORDER_SIZE}" "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE" $JQ --argjson go "$GAPS_OUT" --argjson gi "$GAPS_IN" --argjson bs "$BORDER_SIZE" '.hyprland = {"gaps_out": $go, "gaps_in": $gi, "border_size": $bs}' "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE"
rm "$HYPRLAND_STATE_FILE" rm "$HYPRLAND_STATE_FILE"
fi fi
# 3. Migrate plaintext string state files # 3. Migrate plaintext string state files
if [[ -f $THEME_STATE_FILE ]]; then if [[ -f $THEME_STATE_FILE ]]; then
THEME=$(cat "$THEME_STATE_FILE" | tr -d '\n') THEME=$(cat "$THEME_STATE_FILE" | tr -d '\n')
$JQ ".theme = \"$THEME\"" "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE" $JQ --arg theme "$THEME" '.theme = $theme' "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE"
rm "$THEME_STATE_FILE" rm "$THEME_STATE_FILE"
fi fi
if [[ -f $WALLPAPER_STATE_FILE ]]; then if [[ -f $WALLPAPER_STATE_FILE ]]; then
WALLPAPER=$(cat "$WALLPAPER_STATE_FILE" | tr -d '\n') WALLPAPER=$(cat "$WALLPAPER_STATE_FILE" | tr -d '\n')
$JQ ".wallpaper = \"$WALLPAPER\"" "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE" $JQ --arg wp "$WALLPAPER" '.wallpaper = $wp' "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE"
rm "$WALLPAPER_STATE_FILE" rm "$WALLPAPER_STATE_FILE"
fi fi
if [[ -f $FONT_STATE_FILE ]]; then if [[ -f $FONT_STATE_FILE ]]; then
FONT=$(cat "$FONT_STATE_FILE" | tr -d '\n') FONT=$(cat "$FONT_STATE_FILE" | tr -d '\n')
$JQ ".font = \"$FONT\"" "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE" $JQ --arg font "$FONT" '.font = $font' "$NEW_STATE_FILE" > "$NEW_STATE_FILE.tmp" && mv "$NEW_STATE_FILE.tmp" "$NEW_STATE_FILE"
rm "$FONT_STATE_FILE" rm "$FONT_STATE_FILE"
fi fi

View File

@@ -13,7 +13,7 @@ fi
case "$dns" in case "$dns" in
Cloudflare|Google|DHCP) Cloudflare|Google|DHCP)
sudo jq ".dns = \"$dns\"" "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE" sudo jq --arg dns "$dns" '.dns = $dns' "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE"
;; ;;
Custom) Custom)
@@ -24,10 +24,10 @@ Custom)
echo "Error: No DNS servers provided." echo "Error: No DNS servers provided."
exit 1 exit 1
fi fi
# Convert to JSON array # Convert to JSON array safely
dns_array=$(echo "$dns_servers" | jq -R 'split(" ")') dns_array=$(echo "$dns_servers" | jq -R 'split(" ")')
sudo jq ".dns = \"Custom\" | .customDns = $dns_array" "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE" sudo jq --arg dns "Custom" --argjson servers "$dns_array" '.dns = $dns | .customDns = $servers' "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE"
;; ;;
esac esac

View File

@@ -5,13 +5,13 @@
STATE_FILE="/etc/nixos/state.json" STATE_FILE="/etc/nixos/state.json"
if [[ "--remove" == $1 ]]; then if [[ "--remove" == $1 ]]; then
sudo jq ".features.fido2 = false" "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE" sudo jq '.features.fido2 = false' "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE"
echo "FIDO2 support disabled. Applying changes..." echo "FIDO2 support disabled. Applying changes..."
sudo sys-update sudo sys-update
exit 0 exit 0
fi fi
sudo jq ".features.fido2 = true" "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE" sudo jq '.features.fido2 = true' "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE"
echo "FIDO2 support enabled. Applying changes..." echo "FIDO2 support enabled. Applying changes..."
sudo sys-update sudo sys-update

View File

@@ -5,13 +5,13 @@
STATE_FILE="/etc/nixos/state.json" STATE_FILE="/etc/nixos/state.json"
if [[ "--remove" == $1 ]]; then if [[ "--remove" == $1 ]]; then
sudo jq ".features.fingerprint = false" "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE" sudo jq '.features.fingerprint = false' "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE"
echo "Fingerprint support disabled. Applying changes..." echo "Fingerprint support disabled. Applying changes..."
sudo sys-update sudo sys-update
exit 0 exit 0
fi fi
sudo jq ".features.fingerprint = true" "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE" sudo jq '.features.fingerprint = true' "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE"
echo "Fingerprint support enabled. Applying changes..." echo "Fingerprint support enabled. Applying changes..."
sudo sys-update sudo sys-update

View File

@@ -3,24 +3,25 @@
# Toggles the idle daemon (hypridle) between enabled and disabled. # Toggles the idle daemon (hypridle) between enabled and disabled.
# Hybrid: updates state.json and provides instant feedback. # Hybrid: updates state.json and provides instant feedback.
STATE_FILE="$HOME/.config/home-manager/state.json" STATE_DIR="$HOME/.config/nomarchy"
mkdir -p "$(dirname "$STATE_FILE")" STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR"
# Initialize if doesn't exist # Initialize if doesn't exist
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE" [[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
if [[ $NNOMARCHY_TOGGLE_IDLE == "false" ]]; then if [[ $NOMARCHY_TOGGLE_IDLE == "false" ]]; then
NEW_VALUE="true" NEW_VALUE="true"
setsid hypridle >/dev/null 2>&1 & setsid hypridle >/dev/null 2>&1 &
notify-send -u low "󱫖 Now locking computer when idle" notify-send -u low " Now locking computer when idle"
else else
NEW_VALUE="false" NEW_VALUE="false"
pkill -x hypridle pkill -x hypridle
notify-send -u low "󱫖 Stop locking computer when idle" notify-send -u low " Stop locking computer when idle"
fi fi
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".idle = $NEW_VALUE" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --argjson val "$NEW_VALUE" '.idle = $val' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Idle state set to $NEW_VALUE. Environment will be fully updated on next rebuild." echo "Idle state set to $NEW_VALUE. Environment will be fully updated on next rebuild."

View File

@@ -3,25 +3,25 @@
# Toggles the suspend menu option availability. # Toggles the suspend menu option availability.
# Hybrid: updates state.json and runs env-update for persistence. # Hybrid: updates state.json and runs env-update for persistence.
STATE_FILE="$HOME/.config/home-manager/state.json" STATE_DIR="$HOME/.config/nomarchy"
mkdir -p "$(dirname "$STATE_FILE")" STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR"
# Initialize if doesn't exist # Initialize if doesn't exist
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE" [[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
# Get current state from env or state file # Get current state from env or state file
if [[ $NNOMARCHY_TOGGLE_SUSPEND == "false" ]]; then if [[ $NOMARCHY_TOGGLE_SUSPEND == "false" ]]; then
NEW_VALUE="true" NEW_VALUE="true"
notify-send -u low "󰒲 Suspend now available in system menu" notify-send -u low " Suspend now available in system menu"
else else
NEW_VALUE="false" NEW_VALUE="false"
notify-send -u low "󰒲 Suspend removed from system menu" notify-send -u low " Suspend removed from system menu"
fi fi
# Update JSON using jq # Update JSON using jq with --argjson for proper boolean handling
# We use a temporary file to avoid corruption if the shell is interrupted
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".suspend = $NEW_VALUE" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --argjson val "$NEW_VALUE" '.suspend = $val' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Suspend availability set to $NEW_VALUE. Updating environment..." echo "Suspend availability set to $NEW_VALUE. Updating environment..."

View File

@@ -6,7 +6,7 @@ STATE_FILE="/etc/nixos/state.json"
timezone=$(timedatectl list-timezones | gum filter --height 20 --header "Set timezone") || exit 1 timezone=$(timedatectl list-timezones | gum filter --height 20 --header "Set timezone") || exit 1
sudo jq ".timezone = \"$timezone\"" "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE" sudo jq --arg tz "$timezone" '.timezone = $tz' "$STATE_FILE" > /tmp/state.json && sudo mv /tmp/state.json "$STATE_FILE"
echo "Timezone is now set to $timezone. Applying changes..." echo "Timezone is now set to $timezone. Applying changes..."
sudo sys-update sudo sys-update

View File

@@ -247,6 +247,7 @@ show_setup_config_menu() {
*Walker*) open_in_editor ~/.config/walker/config.toml && nomarchy-restart-walker ;; *Walker*) open_in_editor ~/.config/walker/config.toml && nomarchy-restart-walker ;;
*Waybar*) open_in_editor ~/.config/waybar/config.jsonc && nomarchy-restart-waybar ;; *Waybar*) open_in_editor ~/.config/waybar/config.jsonc && nomarchy-restart-waybar ;;
*XCompose*) open_in_editor ~/.XCompose && nomarchy-restart-xcompose ;; *XCompose*) open_in_editor ~/.XCompose && nomarchy-restart-xcompose ;;
*Overrides*) xdg-open ~/.config/nomarchy/overrides/ ;;
*) show_setup_menu ;; *) show_setup_menu ;;
esac esac
} }

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Nomarchy State Migration Script
# Migrates state from old locations to unified ~/.config/nomarchy/state.json
set -e
NEW_STATE_DIR="$HOME/.config/nomarchy"
NEW_STATE_FILE="$NEW_STATE_DIR/state.json"
OLD_HOME_STATE="$HOME/.config/home-manager/state.json"
OLD_SYSTEM_STATE="/etc/nixos/state.json"
mkdir -p "$NEW_STATE_DIR"
# Initialize new state file if it doesn't exist
if [[ ! -f "$NEW_STATE_FILE" ]]; then
echo "{}" > "$NEW_STATE_FILE"
fi
# Function to safely merge JSON
merge_json() {
local source="$1"
if [[ -f "$source" ]]; then
echo "Migrating from $source..."
TMP_FILE=$(mktemp)
# Merge source into new state (new state values take precedence if conflict)
jq -s '.[0] * .[1]' "$source" "$NEW_STATE_FILE" > "$TMP_FILE" && mv "$TMP_FILE" "$NEW_STATE_FILE"
fi
}
# Migrate old home-manager state
if [[ -f "$OLD_HOME_STATE" ]] && [[ "$OLD_HOME_STATE" != "$NEW_STATE_FILE" ]]; then
merge_json "$OLD_HOME_STATE"
echo "Old home state migrated. You can remove: $OLD_HOME_STATE"
fi
# Check if system state exists and user wants to sync it
if [[ -f "$OLD_SYSTEM_STATE" ]]; then
echo ""
echo "System state found at $OLD_SYSTEM_STATE"
echo "Note: System state will continue to be read from /etc/nixos/state.json"
echo " for system-level NixOS configuration."
fi
# Run the preflight migration for any legacy formats
if command -v nomarchy-preflight-migration &> /dev/null; then
nomarchy-preflight-migration
fi
echo ""
echo "Migration complete!"
echo "New state location: $NEW_STATE_FILE"
echo ""
echo "Current state:"
jq '.' "$NEW_STATE_FILE"

100
bin/utils/nomarchy-state-write Executable file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# Nomarchy Atomic State Write Helper
# Provides atomic JSON state updates with flock to prevent race conditions
#
# Usage:
# nomarchy-state-write <key> <value> [--type string|bool|number|json]
# nomarchy-state-write --file <path> <key> <value> [--type ...]
#
# Examples:
# nomarchy-state-write theme "nord"
# nomarchy-state-write idle true --type bool
# nomarchy-state-write hyprland '{"gaps_in": 5}' --type json
# nomarchy-state-write --file /etc/nixos/state.json dns "Cloudflare"
set -e
# Default state file
STATE_FILE="$HOME/.config/nomarchy/state.json"
VALUE_TYPE="string"
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--file)
STATE_FILE="$2"
shift 2
;;
--type)
VALUE_TYPE="$2"
shift 2
;;
*)
if [[ -z "$KEY" ]]; then
KEY="$1"
elif [[ -z "$VALUE" ]]; then
VALUE="$1"
fi
shift
;;
esac
done
if [[ -z "$KEY" ]] || [[ -z "$VALUE" ]]; then
echo "Usage: nomarchy-state-write <key> <value> [--type string|bool|number|json]"
echo " nomarchy-state-write --file <path> <key> <value> [--type ...]"
exit 1
fi
# Ensure directory exists
mkdir -p "$(dirname "$STATE_FILE")"
# Initialize file if it doesn't exist
[[ ! -f "$STATE_FILE" ]] && echo "{}" > "$STATE_FILE"
# Create lock file path
LOCK_FILE="${STATE_FILE}.lock"
# Perform atomic update with flock
(
flock -x 200
TMP_FILE=$(mktemp)
trap "rm -f '$TMP_FILE'" EXIT
case "$VALUE_TYPE" in
string)
jq --arg val "$VALUE" ".$KEY = \$val" "$STATE_FILE" > "$TMP_FILE"
;;
bool)
if [[ "$VALUE" == "true" ]] || [[ "$VALUE" == "false" ]]; then
jq --argjson val "$VALUE" ".$KEY = \$val" "$STATE_FILE" > "$TMP_FILE"
else
echo "Error: Boolean value must be 'true' or 'false'"
exit 1
fi
;;
number)
jq --argjson val "$VALUE" ".$KEY = \$val" "$STATE_FILE" > "$TMP_FILE"
;;
json)
jq --argjson val "$VALUE" ".$KEY = \$val" "$STATE_FILE" > "$TMP_FILE"
;;
*)
echo "Error: Unknown type '$VALUE_TYPE'. Use string, bool, number, or json."
exit 1
;;
esac
# Validate JSON before moving
if jq empty "$TMP_FILE" 2>/dev/null; then
mv "$TMP_FILE" "$STATE_FILE"
else
echo "Error: Failed to generate valid JSON"
exit 1
fi
) 200>"$LOCK_FILE"
# Clean up lock file
rm -f "$LOCK_FILE"

View File

@@ -2,8 +2,9 @@
# Toggles the window gaps globally between no gaps and the default 10/5/2, declaratively and instantly. # Toggles the window gaps globally between no gaps and the default 10/5/2, declaratively and instantly.
STATE_FILE="$HOME/.config/home-manager/state.json" STATE_DIR="$HOME/.config/nomarchy"
mkdir -p "$(dirname "$STATE_FILE")" STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR"
if [ ! -f "$STATE_FILE" ]; then if [ ! -f "$STATE_FILE" ]; then
echo "{}" > "$STATE_FILE" echo "{}" > "$STATE_FILE"
@@ -24,6 +25,6 @@ else
fi fi
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".hyprland = $NEW_STATE" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --argjson state "$NEW_STATE" '.hyprland = $state' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Toggled gaps to $NEW_STATE declaratively." echo "Toggled gaps to $NEW_STATE declaratively."

View File

@@ -3,22 +3,23 @@
# Toggles the screensaver availability. # Toggles the screensaver availability.
# Hybrid: updates state.json and runs env-update for persistence. # Hybrid: updates state.json and runs env-update for persistence.
STATE_FILE="$HOME/.config/home-manager/state.json" STATE_DIR="$HOME/.config/nomarchy"
mkdir -p "$(dirname "$STATE_FILE")" STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR"
# Initialize if doesn't exist # Initialize if doesn't exist
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE" [[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
if [[ $NNOMARCHY_TOGGLE_SCREENSAVER == "false" ]]; then if [[ $NOMARCHY_TOGGLE_SCREENSAVER == "false" ]]; then
NEW_VALUE="true" NEW_VALUE="true"
notify-send -u low "󱄄 Screensaver enabled" notify-send -u low " Screensaver enabled"
else else
NEW_VALUE="false" NEW_VALUE="false"
notify-send -u low "󱄄 Screensaver disabled" notify-send -u low " Screensaver disabled"
fi fi
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".screensaver = $NEW_VALUE" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --argjson val "$NEW_VALUE" '.screensaver = $val' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Screensaver state set to $NEW_VALUE. Updating environment..." echo "Screensaver state set to $NEW_VALUE. Updating environment..."

View File

@@ -3,23 +3,24 @@
# Toggles the waybar top bar. # Toggles the waybar top bar.
# Hybrid: updates state.json and provides instant feedback. # Hybrid: updates state.json and provides instant feedback.
STATE_FILE="$HOME/.config/home-manager/state.json" STATE_DIR="$HOME/.config/nomarchy"
mkdir -p "$(dirname "$STATE_FILE")" STATE_FILE="$STATE_DIR/state.json"
mkdir -p "$STATE_DIR"
# Initialize if doesn't exist # Initialize if doesn't exist
[[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE" [[ ! -f $STATE_FILE ]] && echo "{}" > "$STATE_FILE"
if [[ $NNOMARCHY_TOGGLE_WAYBAR == "false" ]]; then if [[ $NOMARCHY_TOGGLE_WAYBAR == "false" ]]; then
NEW_VALUE="true" NEW_VALUE="true"
uwsm-app -- waybar >/dev/null 2>&1 & uwsm-app -- waybar >/dev/null 2>&1 &
notify-send -u low "󰍜 Top bar enabled" notify-send -u low " Top bar enabled"
else else
NEW_VALUE="false" NEW_VALUE="false"
pkill -x waybar pkill -x waybar
notify-send -u low "󰍜 Top bar disabled" notify-send -u low " Top bar disabled"
fi fi
TMP_JSON=$(mktemp) TMP_JSON=$(mktemp)
jq ".waybar = $NEW_VALUE" "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE" jq --argjson val "$NEW_VALUE" '.waybar = $val' "$STATE_FILE" > "$TMP_JSON" && mv "$TMP_JSON" "$STATE_FILE"
echo "Waybar state set to $NEW_VALUE. Environment will be fully updated on next rebuild." echo "Waybar state set to $NEW_VALUE. Environment will be fully updated on next rebuild."

Some files were not shown because too many files have changed in this diff Show More