# 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. # # Auto-login is in-flake state like the rest (settings.greeter.autoLogin, # written by System › Auto-login via nomarchy-autologin below), NOT a baked # line in system.nix: a hand-set `nomarchy.system.greeter.autoLogin` outranks # the state default, which would leave the menu toggle flipping JSON that # nothing reads. The installer therefore seeds the STATE on LUKS machines and # the template keeps its example commented (templates/downstream/system.nix). { config, lib, pkgs, ... }: let cfg = config.nomarchy.system; distroName = config.system.nixos.distroName; sync = lib.getExe pkgs.nomarchy-theme-sync; # Menu/CLI toggle, same shape as nomarchy-autotimezone: runs as the normal # user (it owns the flake checkout + writes the state), sudos only the # system switch. greetd's initial_session is baked at system rebuild, so # there is nothing to apply live — the next boot is the observable change. nomarchy-autologin = pkgs.writeShellScriptBin "nomarchy-autologin" '' set -e if [ "$(id -u)" -eq 0 ]; then echo "nomarchy-autologin: run as your normal user (it sudos the rebuild itself)" >&2 exit 1 fi flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}" cur=$(${sync} get settings.greeter.autoLogin 2>/dev/null) || cur=null case "''${1:-toggle}" in on) new="\"$USER\"" ;; off) new=null ;; toggle) case "$cur" in null|""|None) new="\"$USER\"" ;; *) new=null ;; esac ;; status) echo "$cur"; exit 0 ;; *) echo "usage: nomarchy-autologin [toggle|on|off|status]" >&2; exit 64 ;; esac ${sync} --quiet set settings.greeter.autoLogin "$new" --no-switch notify-send "Auto-login" "Rebuilding the system…" 2>/dev/null || true sudo nixos-rebuild switch --flake "$flake#default" if [ "$new" = null ]; then notify-send "Auto-login off" "The greeter asks who you are on the next boot." 2>/dev/null || true else notify-send "Auto-login on" "Next boot goes straight to the desktop." 2>/dev/null || true fi ''; state = if cfg.stateFile != null then builtins.fromJSON (builtins.readFile cfg.stateFile) else { }; # The auto-login user from the state, or null. Read here via the state file # — NOT `config.nomarchy.settings`, which exists only on the Home Manager # side: on NixOS that attribute is missing, and `or null` swallows the # error, so the old default silently evaluated to null on every machine. stateAutoLogin = let v = (state.settings or { }).greeter.autoLogin or null; in if builtins.isString v && v != "" then v else null; # 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 = { # Shipped unconditionally so the menu can turn auto-login back ON while # it's off — the same reason nomarchy-autotimezone is unconditional. environment.systemPackages = [ nomarchy-autologin ]; # Track the in-flake flag; mkDefault so a hand-set # nomarchy.system.greeter.autoLogin in system.nix still wins (the # autoTimezone pattern). nomarchy.system.greeter.autoLogin = lib.mkDefault stateAutoLogin; # 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; }; }; }; }; }