feat(greeter): tuigreet + console themed from the state JSON (item 16)
Some checks failed
Check / eval (push) Has been cancelled

New modules/nixos/greeter.nix owns the greetd block (moved out of
default.nix) plus the theming, the Plymouth model — baked at SYSTEM
rebuild from nomarchy.system.stateFile. Two parts, because tuigreet
draws on the VT with the 16 ANSI slots: console.colors becomes the
theme's ansi[] (vt.default_* kernel params — which also themes raw
ttys and the LUKS passphrase prompt), and tuigreet gets a --theme of
NAMED slots (its parser is ratatui Color::from_str, checked in source;
the names map to the standard indexes the retinted palette now
serves). A sparse state without ansi skips theming cleanly.

Verified: V0; V1 — the template-system toplevel bakes the full --theme
spec into greetd.toml and all three vt.default_{red,grn,blu} palettes
into kernel-params. A checks.greeter VM test was attempted and dropped
after bisecting: tuigreet dies under runNixOSTest even without the
flag (harness limit; nixpkgs's own greetd test uses agreety) —
recorded in agent/MEMORY.md. Remains: V3 rendering +
theme-follows-rebuild on hardware (HARDWARE-QUEUE).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-07-05 18:37:32 +01:00
parent 1921839e0e
commit a360bc87ca
6 changed files with 108 additions and 21 deletions

View File

@@ -22,7 +22,7 @@ let
'';
in
{
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ./hardware.nix ./timezone.nix ./oom.nix ];
imports = [ ./options.nix ./plymouth.nix ./greeter.nix ./file-manager.nix ./power.nix ./services.nix ./hardware.nix ./timezone.nix ./oom.nix ];
config = {
# Distro branding. distroName flows into /etc/os-release PRETTY_NAME,
@@ -99,22 +99,8 @@ in
extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; # file pickers, etc.
};
services.greetd = lib.mkIf cfg.greeter.enable {
enable = lib.mkDefault true;
settings = {
default_session = {
# start-hyprland is Hyprland 0.55's watchdog launcher; running
# the bare binary makes every session print a warning.
command = lib.mkDefault "${pkgs.tuigreet}/bin/tuigreet --time --remember --greeting 'Welcome to ${distroName}' --cmd start-hyprland";
user = "greeter";
};
# Boot straight into the session once; logout → normal greeter.
initial_session = lib.mkIf (cfg.greeter.autoLogin != null) {
command = "start-hyprland";
user = cfg.greeter.autoLogin;
};
};
};
# The greetd/tuigreet login screen lives in ./greeter.nix — themed
# from the state JSON (console.colors + --theme) at system rebuild.
# ── Audio: Pipewire ──────────────────────────────────────────────
security.rtkit.enable = lib.mkDefault cfg.audio.enable;

70
modules/nixos/greeter.nix Normal file
View File

@@ -0,0 +1,70 @@
# Greeter — greetd/tuigreet, themed from the same theme-state.json that
# drives the desktop (nomarchy.system.stateFile; the Plymouth model:
# baked at SYSTEM rebuild, so it follows the theme as of the last
# sys-update, not the last instant apply).
#
# tuigreet draws on the virtual console with the 16 ANSI slots, so the
# theming is two-part:
# 1. console.colors — the VT palette becomes the theme's ansi[] hexes
# (which also themes raw ttys and the LUKS passphrase prompt: the
# same JSON reaches every pre-session surface).
# 2. --theme — tuigreet components on NAMED slots (its parser is
# ratatui Color::from_str; names map to the standard indexes, e.g.
# blue=4, gray=7, white=15, so the palette above hands them the
# theme's colors). ANSI "black" stays dark even in light themes —
# the greeter reads terminal-dark there, the same convention every
# terminal applies to ANSI colors.
{ config, lib, pkgs, ... }:
let
cfg = config.nomarchy.system;
distroName = config.system.nixos.distroName;
state =
if cfg.stateFile != null
then builtins.fromJSON (builtins.readFile cfg.stateFile)
else { };
# A sparse/hand-rolled state without a proper ansi block just skips the
# theming (stock tuigreet grey) — never an eval error.
ansi = state.ansi or [ ];
themed = builtins.isList ansi && builtins.length ansi == 16;
tuigreetTheme = lib.concatStringsSep ";" [
"container=black" # ansi[0] — the theme's terminal background
"border=blue" # ansi[4] — the accent family in every shipped palette
"title=cyan"
"greet=cyan"
"prompt=green"
"input=white" # ansi[15] — bright foreground
"action=blue"
"button=yellow"
"time=cyan"
"text=gray" # ansi[7] — muted foreground
];
in
{
config = {
# VT palette from the theme (RRGGBB, no #; lands as vt.default_* kernel
# params). mkDefault so a downstream console.colors wins.
console.colors = lib.mkIf themed (lib.mkDefault (map (lib.removePrefix "#") ansi));
services.greetd = lib.mkIf cfg.greeter.enable {
enable = lib.mkDefault true;
settings = {
default_session = {
# start-hyprland is Hyprland 0.55's watchdog launcher; running
# the bare binary makes every session print a warning.
command = lib.mkDefault ("${pkgs.tuigreet}/bin/tuigreet --time --remember --greeting 'Welcome to ${distroName}'"
+ lib.optionalString themed " --theme '${tuigreetTheme}'"
+ " --cmd start-hyprland");
user = "greeter";
};
# Boot straight into the session once; logout → normal greeter.
initial_session = lib.mkIf (cfg.greeter.autoLogin != null) {
command = "start-hyprland";
user = cfg.greeter.autoLogin;
};
};
};
};
}