From 2ecf842a1b4982cfa6840079c333f3dad9b3539b Mon Sep 17 00:00:00 2001 From: iris Date: Mon, 24 Aug 2026 12:41:49 +0200 Subject: [PATCH] swarm-ui: apply the operator's stylix theme, same as the dashboard already does mara: "swarm dash is in my own colors, but tab colors on swarm ui are the default catpucchin one." Root cause: hive-c0re/theme.nix's stylix overlay only ever wrote a themed colors.css onto the dashboard/agent frontend subtrees -- swarm-ui, served from its own separate package, was never in scope. Extracts the stylix-detection + colors.css-generation logic (previously inline in theme.nix) into a shared nix/host-modules/stylix-theme.nix, imported by both theme.nix and swarm-ui.nix -- one source instead of a second copy that has to agree by inspection. swarm-ui.nix gains its own themedPackage overlay (same shape as theme.nix's themedFrontend: copy the package, overwrite static/colors.css) and serves that instead of cfg.package directly when stylix is active; a clean passthrough otherwise. Verified with a throwaway nixosSystem eval (this repo's own flake checks do not exercise gateway-module wiring) confirming the unthemed path resolves cfg.package unchanged. --- nix/host-modules/hive-c0re/theme.nix | 33 +++------------- nix/host-modules/stylix-theme.nix | 59 ++++++++++++++++++++++++++++ nix/host-modules/swarm-ui.nix | 23 ++++++++++- 3 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 nix/host-modules/stylix-theme.nix diff --git a/nix/host-modules/hive-c0re/theme.nix b/nix/host-modules/hive-c0re/theme.nix index 7a3c7023..0141446e 100644 --- a/nix/host-modules/hive-c0re/theme.nix +++ b/nix/host-modules/hive-c0re/theme.nix @@ -7,6 +7,10 @@ # from the 16 base16 slots (see docs/web-ui/css-vars.md). The guarded # access makes this a clean no-op when stylix isn't imported into the # host config. Exposed as the read-only `c0re.servedFrontend` option. +# +# Detection + CSS-generation itself lives in `../stylix-theme.nix` +# (shared with `swarm-ui.nix`) — this file is the c0re-specific overlay +# consumer (which subtrees, which option). { pkgs, lib, @@ -15,33 +19,8 @@ }: let cfg = config.services.hyperhive.c0re; - stylixThemeColors = - if (config.stylix.enable or false) && ((config.lib.stylix or { }) ? colors) then - config.lib.stylix.colors.withHashtag - else - null; - themedColorsCss = - c: - pkgs.writeText "hyperhive-colors.css" '' - :root { - --base00: ${c.base00}; - --base01: ${c.base01}; - --base02: ${c.base02}; - --base03: ${c.base03}; - --base04: ${c.base04}; - --base05: ${c.base05}; - --base06: ${c.base06}; - --base07: ${c.base07}; - --base08: ${c.base08}; - --base09: ${c.base09}; - --base0A: ${c.base0A}; - --base0B: ${c.base0B}; - --base0C: ${c.base0C}; - --base0D: ${c.base0D}; - --base0E: ${c.base0E}; - --base0F: ${c.base0F}; - } - ''; + stylixTheme = import ../stylix-theme.nix { inherit lib config pkgs; }; + inherit (stylixTheme) stylixThemeColors themedColorsCss; # Overlay the generated colors.css onto both dist subtrees. Both the # dashboard (served by hive-c0re via HIVE_STATIC_DIR) and the agent UIs # (served by the gateway from HIVE_AGENT_FRONTEND_DIR — static files diff --git a/nix/host-modules/stylix-theme.nix b/nix/host-modules/stylix-theme.nix new file mode 100644 index 00000000..71479eef --- /dev/null +++ b/nix/host-modules/stylix-theme.nix @@ -0,0 +1,59 @@ +# Shared stylix theming helper: detects an active stylix palette on the +# host and renders it as a base16 `colors.css` overlay file. `colors.css` +# is the entire theme-swap contract for every frontend bundle in this +# project (`theme.css` derives every semantic var from these 16 slots — +# see docs/web-ui/css-vars.md), so "apply the operator's stylix palette" +# always reduces to "write this one file over the prebuilt dist's own +# `colors.css`" — no npm/esbuild rebuild needed. +# +# Two consumers today: `hive-c0re/theme.nix` (dashboard + agent +# frontend), `swarm-ui.nix` (the swarm-level UI). Factored out here +# rather than duplicated in both — same "one source, not a copy that +# agrees by inspection" reasoning `hive-gateway/vhost-lib.nix` gives for +# its own split. Both callers `import` this directly (not published via +# an option): unlike the gateway's vhost kit, this needs no per-service +# knowledge to construct, it's a pure read of the host's own top-level +# `config` — an option indirection would add a layer with nothing to +# publish through it. +# +# Guarded access makes `stylixThemeColors` a clean `null` when stylix +# isn't imported into the host config at all, so every caller's own +# `if stylixThemeColors != null then ... else ` +# stays a zero-op when the operator hasn't opted in. +{ + lib, + config, + pkgs, +}: +let + stylixThemeColors = + if (config.stylix.enable or false) && ((config.lib.stylix or { }) ? colors) then + config.lib.stylix.colors.withHashtag + else + null; + themedColorsCss = + c: + pkgs.writeText "hyperhive-colors.css" '' + :root { + --base00: ${c.base00}; + --base01: ${c.base01}; + --base02: ${c.base02}; + --base03: ${c.base03}; + --base04: ${c.base04}; + --base05: ${c.base05}; + --base06: ${c.base06}; + --base07: ${c.base07}; + --base08: ${c.base08}; + --base09: ${c.base09}; + --base0A: ${c.base0A}; + --base0B: ${c.base0B}; + --base0C: ${c.base0C}; + --base0D: ${c.base0D}; + --base0E: ${c.base0E}; + --base0F: ${c.base0F}; + } + ''; +in +{ + inherit stylixThemeColors themedColorsCss; +} diff --git a/nix/host-modules/swarm-ui.nix b/nix/host-modules/swarm-ui.nix index 662d39b7..8c7e96af 100644 --- a/nix/host-modules/swarm-ui.nix +++ b/nix/host-modules/swarm-ui.nix @@ -9,6 +9,7 @@ { lib, config, + pkgs, ... }: let @@ -17,6 +18,26 @@ let autheliaCfg = config.services.hyperhive.swarm.authelia; controllerCfg = config.services.hyperhive.swarm.controller; + # Same stylix auto-theming `hive-c0re/theme.nix` gives the dashboard — + # this UI was left out of that overlay entirely (an operator with a + # stylix-themed host saw the dashboard in their own colours but this + # UI still on the default Catppuccin palette), because nothing here + # ever wrote a themed `colors.css` over `cfg.package` before serving + # it; the c0re-side overlay only ever touched `dashboard/`/`agent/`. + # Detection + CSS-generation is shared (`./stylix-theme.nix`); + # `servedPackage` is this module's own overlay-onto-`cfg.package` + # consumer, same shape as `theme.nix`'s `themedFrontend`. + stylixTheme = import ./stylix-theme.nix { inherit lib config pkgs; }; + inherit (stylixTheme) stylixThemeColors themedColorsCss; + themedPackage = + c: + pkgs.runCommand "hyperhive-swarm-ui-themed" { } '' + cp -r ${cfg.package} $out + chmod -R u+w $out + install -m644 ${themedColorsCss c} $out/static/colors.css + ''; + servedPackage = if stylixThemeColors != null then themedPackage stylixThemeColors else cfg.package; + # Repeated verbatim by every location that should be operator-gated # (`/`, `/api/`, `/api/docs/`) rather than set once on the server: # nginx's `auth_request` does NOT inherit across sibling locations, so @@ -170,7 +191,7 @@ in extraConfig = gatewayCfg.lib.securityHeaders; locations = { "/" = { - root = "${cfg.package}"; + root = "${servedPackage}"; extraConfig = '' ${swarmAuthRequest} # SPA: any path the bundle routes client-side is served the