From 27409eb5fab67f244bfe97bf1565f1ab22942403 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 3 Jul 2026 17:31:31 +0200 Subject: [PATCH] Migrate to hm managed theme switch --- flake.nix | 1 - hm-modules/theme/default.nix | 70 +++++++++++++++++++--- hm-modules/theme/theme-switch.sh | 77 +++++++++++++++++++++++++ machines/fujin/main/default.nix | 33 ++--------- machines/fujin/main/home.nix | 28 ++++++++- modules/default.nix | 1 - modules/theme/default.nix | 65 --------------------- modules/window-manager/hypr/default.nix | 26 --------- 8 files changed, 168 insertions(+), 133 deletions(-) create mode 100644 hm-modules/theme/theme-switch.sh delete mode 100644 modules/theme/default.nix diff --git a/flake.nix b/flake.nix index 02bb2a6..225531f 100644 --- a/flake.nix +++ b/flake.nix @@ -199,7 +199,6 @@ # Applications inputs.copyparty.nixosModules.default inputs.vscode-server.nixosModules.default - inputs.stylix.nixosModules.stylix inputs.nix-ld.nixosModules.nix-ld inputs.minegrub-theme.nixosModules.default inputs.backlog.nixosModules.default diff --git a/hm-modules/theme/default.nix b/hm-modules/theme/default.nix index e3c8ea0..d83ea7a 100644 --- a/hm-modules/theme/default.nix +++ b/hm-modules/theme/default.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, options, ... }: with lib; @@ -18,6 +18,35 @@ let }; }; + # Generate all theme-variant combinations + themeVariants = concatMapAttrs (name: theme: { + "${name}-dark" = { scheme = theme.dark; polarity = "dark"; }; + "${name}-light" = { scheme = theme.light; polarity = "light"; }; + }) cfg.themes; + + # Base is gruvbox-dark, everything else is a specialisation + baseVariant = "gruvbox-dark"; + specialisationVariants = filterAttrs (name: _: name != baseVariant) themeVariants; + + availableVariants = concatStringsSep " " (attrNames themeVariants); + + # Userspace theme switcher: activates a pre-built Home Manager + # specialisation generation. No sudo, no OS rebuild — just runs the + # specialisation's activation script in the base HM generation. + # + # The script logic lives in ./theme-switch.sh (plain shell, no Nix + # escaping); we only inject the two config-derived values it needs. + theme-switch = pkgs.writeShellScriptBin "theme-switch" '' + baseVariant="${baseVariant}" + availableVariants="${availableVariants}" + ${builtins.readFile ./theme-switch.sh} + ''; + + # Stylix is only declared on hosts that import its Home Manager module + # (e.g. `fujin`). Servers (susano, izanagi, amaterasu, ...) don't, so + # `stylix` isn't declared there. We probe option *declarations* (not + # config values) to avoid an evaluation cycle. + hasStylix = options ? stylix; in { options.dov.dynamic-theme = { enable = mkEnableOption "dynamic theme switching"; @@ -40,12 +69,35 @@ in { }; }; - config = mkIf cfg.enable { - # HM-level stylix config inherits from NixOS level - # Specialisations are handled by NixOS module - stylix = { - enable = true; - autoEnable = true; - }; - }; + config = mkIf cfg.enable ( + { + home.packages = [ theme-switch ]; + } + # Only reference the `stylix` option where it is actually declared. + # `mkIf false` is NOT enough: the module system still rejects a + # definition (even a disabled one) for an option that does not exist, + # which breaks every host that doesn't import stylix. `optionalAttrs` + # removes the key structurally, so non-stylix hosts evaluate cleanly. + // optionalAttrs hasStylix { + stylix = { + enable = true; + autoEnable = true; + # Base scheme/polarity; overridable per specialisation below. + base16Scheme = mkDefault themeVariants.${baseVariant}.scheme; + polarity = mkDefault themeVariants.${baseVariant}.polarity; + }; + + # Generate Home Manager specialisations for every non-base variant. + # Each is pre-built into the nix store during the single rebuild and + # activated userspace via `theme-switch` (no sudo, no OS rebuild). + specialisation = mapAttrs (name: variant: { + configuration = { + stylix = { + base16Scheme = mkForce variant.scheme; + polarity = mkForce variant.polarity; + }; + }; + }) specialisationVariants; + } + ); } diff --git a/hm-modules/theme/theme-switch.sh b/hm-modules/theme/theme-switch.sh new file mode 100644 index 0000000..eb155a1 --- /dev/null +++ b/hm-modules/theme/theme-switch.sh @@ -0,0 +1,77 @@ +# shellcheck shell=bash +# theme-switch — activate a pre-built Home Manager specialisation. +# +# Expects two variables to be injected by the Nix wrapper: +# baseVariant the variant that is the generation itself (not a +# specialisation), activated via `$gen/activate`. +# availableVariants space-separated list of all variant names (help text). +# +# Standalone usage for testing: +# baseVariant=gruvbox-dark \ +# availableVariants="gruvbox-dark gruvbox-light catppuccin-dark catppuccin-light" \ +# bash theme-switch.sh + +: "${baseVariant:?theme-switch: baseVariant not set}" +: "${availableVariants:?theme-switch: availableVariants not set}" +set -euo pipefail + +variant="${1:-}" + +if [ -z "$variant" ]; then + echo "Usage: theme-switch " + echo "Available: ${availableVariants}" + exit 1 +fi + +# Resolve the Home Manager *base* generation — the one built by NixOS that +# actually contains the `specialisation/` directory holding every variant's +# activation script. +# +# IMPORTANT: do NOT use `current-home` / the HM profile as the primary +# source. Activating a specialisation repoints those to the specialisation's +# *leaf* generation, which has no `specialisation/` of its own — so after +# switching once, every other variant (and the base) would become unfindable. +# The NixOS-managed `home-manager-«user».service` unit references the base +# generation and only changes on a rebuild. +user="${USER:-$(id -un)}" +gen="" +if unit="$(systemctl cat "home-manager-${user}.service" 2>/dev/null)"; then + gen="$(printf '%s\n' "$unit" | grep -oE '/nix/store/[0-9a-z]+-home-manager-generation' | head -n1)" +fi + +# Fallbacks for standalone Home Manager, where the profile symlink / gcroot +# points at the base generation (not a specialisation leaf). +for candidate in \ + "${HOME}/.local/state/home-manager/gcroots/current-home" \ + "/nix/var/nix/profiles/per-user/${user}/home-manager" \ + "${HOME}/.local/state/home-manager/home-manager-generation" +do + [ -n "$gen" ] && break + if [ -e "$candidate" ]; then + gen="$(readlink -e "$candidate")" || gen="" + fi +done + +if [ -z "$gen" ]; then + echo "error: could not resolve Home Manager base generation" >&2 + exit 1 +fi + +case "$variant" in + "${baseVariant}") + # Base variant is the generation itself, not a specialisation. + activate="$gen/activate" + ;; + *) + activate="$gen/specialisation/$variant/activate" + ;; +esac + +if [ ! -x "$activate" ]; then + echo "error: variant '$variant' not found at $activate" >&2 + echo "Available: ${availableVariants}" + exit 1 +fi + +echo "Switching theme to $variant..." +exec "$activate" diff --git a/machines/fujin/main/default.nix b/machines/fujin/main/default.nix index 31f9a38..0b59dcf 100644 --- a/machines/fujin/main/default.nix +++ b/machines/fujin/main/default.nix @@ -121,8 +121,6 @@ display-manager.ly.enable = true; gaming.enable = true; - - dynamic-theme.enable = true; }; ### @@ -133,37 +131,14 @@ useUserPackages = true; backupFileExtension = "backup"; extraSpecialArgs = { inherit inputs username; }; + # Stylix is handled entirely at the Home Manager level (see + # hm-modules/theme). Load the stylix HM module here so the `stylix` + # option exists for fujin's home configuration. + sharedModules = [ inputs.stylix.homeModules.stylix ]; users."${username}" = { imports = [ ./home.nix ] ++ extraHomeModules; }; }; - stylix = { - enable = true; - # base16Scheme managed by dov.dynamic-theme module - - fonts = { - serif = { - package = pkgs.dejavu_fonts; - name = "DejaVu Serif"; - }; - - sansSerif = { - package = pkgs.dejavu_fonts; - name = "DejaVu Sans"; - }; - - monospace = { - package = pkgs.dejavu_fonts; - name = "DejaVu Sans Mono"; - }; - - emoji = { - package = pkgs.noto-fonts-color-emoji; - name = "Noto Color Emoji"; - }; - }; - }; - fonts.packages = with pkgs; [ noto-fonts diff --git a/machines/fujin/main/home.nix b/machines/fujin/main/home.nix index 6e25d2f..d40f9e3 100644 --- a/machines/fujin/main/home.nix +++ b/machines/fujin/main/home.nix @@ -163,12 +163,36 @@ #mpc-cli ] ++ [ inputs.thefuck.packages.${pkgs.system}.default ]; + # Stylix theming is driven by `dov.dynamic-theme` (hm-modules/theme), + # which sets `enable`, `base16Scheme` and `polarity` + specialisations. + # Here we only keep target overrides and the font set (moved from the + # old NixOS-level stylix config). stylix = { - enable = true; - autoEnable = true; targets = { kde.enable = false; }; + + fonts = { + serif = { + package = pkgs.dejavu_fonts; + name = "DejaVu Serif"; + }; + + sansSerif = { + package = pkgs.dejavu_fonts; + name = "DejaVu Sans"; + }; + + monospace = { + package = pkgs.dejavu_fonts; + name = "DejaVu Sans Mono"; + }; + + emoji = { + package = pkgs.noto-fonts-color-emoji; + name = "Noto Color Emoji"; + }; + }; }; # blutooth applet diff --git a/modules/default.nix b/modules/default.nix index 7025821..39e19c4 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -15,6 +15,5 @@ ./gitlab ./jenkins ./gaming - ./theme ]; } diff --git a/modules/theme/default.nix b/modules/theme/default.nix deleted file mode 100644 index 481c934..0000000 --- a/modules/theme/default.nix +++ /dev/null @@ -1,65 +0,0 @@ -{ config, lib, pkgs, username, ... }: - -with lib; - -let - # Access HM theme config - hmCfg = config.home-manager.users.${username}.dov.dynamic-theme; - - allThemes = hmCfg.themes; - - # Generate all theme-variant combinations - themeVariants = concatMapAttrs (name: theme: { - "${name}-dark" = { scheme = theme.dark; polarity = "dark"; }; - "${name}-light" = { scheme = theme.light; polarity = "light"; }; - }) allThemes; - - # Base is gruvbox-dark, everything else is a specialisation - baseVariant = "gruvbox-dark"; - specialisationVariants = filterAttrs (name: _: name != baseVariant) themeVariants; - - availableVariants = concatStringsSep " " (attrNames themeVariants); - - theme-switch = pkgs.writeShellScriptBin "theme-switch" '' - case "$1" in - ${baseVariant}) - echo "Switching to ${baseVariant}..." - sudo nixos-rebuild switch --flake ~/nixos#${username} - ;; - ${concatStringsSep "\n " (map (name: '' - ${name}) - echo "Switching to ${name}..." - sudo nixos-rebuild switch --flake ~/nixos#${username} --specialisation ${name} - ;;'') (attrNames specialisationVariants))} - *) - echo "Usage: theme-switch " - echo "Available: ${availableVariants}" - exit 1 - ;; - esac - ''; - -in { - options.dov.dynamic-theme.enable = mkEnableOption "NixOS dynamic theme specialisations"; - - config = mkIf (config.dov.dynamic-theme.enable && hmCfg.enable) { - # Add theme-switch script system-wide - environment.systemPackages = [ theme-switch ]; - - # Base theme: gruvbox-dark - stylix = { - base16Scheme = mkDefault themeVariants.${baseVariant}.scheme; - polarity = mkDefault themeVariants.${baseVariant}.polarity; - }; - - # Generate NixOS specialisations for all other variants - specialisation = mapAttrs (name: variant: { - configuration = { - stylix = { - base16Scheme = mkForce variant.scheme; - polarity = mkForce variant.polarity; - }; - }; - }) specialisationVariants; - }; -} diff --git a/modules/window-manager/hypr/default.nix b/modules/window-manager/hypr/default.nix index dcf3ed3..95340dd 100644 --- a/modules/window-manager/hypr/default.nix +++ b/modules/window-manager/hypr/default.nix @@ -3,7 +3,6 @@ with lib; let - colors = config.lib.stylix.colors; cfg = config.dov.window-manager.hypr; hm-cfg = config.home-manager.users.${username}.dov; in { @@ -249,31 +248,6 @@ in { ''; }; - # Stylix base16 palette exported as hyprlang variables. No longer sourced - # from the main Lua config (Lua has no `source`); kept for reference and - # for any hyprlang consumer that wants to source it. - home.file.".config/hypr/colors".text = '' - $background = ${colors.base00} - $foreground = ${colors.base05} - - $color0 = ${colors.base00} - $color1 = ${colors.base01} - $color2 = ${colors.base02} - $color3 = ${colors.base03} - $color4 = ${colors.base04} - $color5 = ${colors.base05} - $color6 = ${colors.base06} - $color7 = ${colors.base07} - $color8 = ${colors.base08} - $color9 = ${colors.base09} - $color10 = ${colors.base0A} - $color11 = ${colors.base0B} - $color12 = ${colors.base0C} - $color13 = ${colors.base0D} - $color14 = ${colors.base0E} - $color15 = ${colors.base0F} - ''; - xdg.configFile."hypr/hyprlock.conf".source = ./hyprlock.conf; xdg.configFile."hypr/hypridle.conf".source = ./hypridle.conf;