All checks were successful
Check / eval (push) Successful in 3m7s
Bernardo, post-reboot: "Use for login" was the wrong question. Whether the
finger works is one decision, not two, and whether login prompts at all is
a different decision that was never in the menu.
System › Fingerprint is now a single Fingerprint (on/off) switch, leading
the menu with enroll/list/verify/delete as the plumbing behind it. It
writes the one settings.fingerprint.pam key, and modules/home/idle.nix now
defaults idle.fingerprint from that same key — so the lock screen and
login/sudo move together instead of drifting apart the way they did until
e2de906. nomarchy-fingerprint does the two rebuilds this needs (sudo
system for PAM, home switch for hyprlock) and refuses to turn on with no
finger enrolled.
System › Auto-login is new (nomarchy-autologin), and it is what decides
whether anything is asked at boot: auto-login on means no prompt whatever
the fingerprint switch says; off means the greeter asks, for a password or
a finger. Installer-seeded ON for LUKS machines — the passphrase already
gates the disk — and off without it, where the greeter is the only thing
between power-on and the desktop.
Both had to become state-owned to be toggleable at all, which surfaced two
real bugs:
* nomarchy.system.greeter.autoLogin defaulted from
`config.nomarchy.settings…` — an attribute that exists ONLY on the Home
Manager side. On NixOS it is absent and `or null` swallowed the error,
so the default silently evaluated to null on every machine ever built.
That is why the installer baked a Nix line: the state path never
worked. Now read via theme-state-read.nix (the hardware.nix/timezone.nix
pattern) and mkDefault'd, so the menu owns it and a hand-set line still
pins it. Two more options read the same phantom bridge — BACKLOG #116.
* `theme-sync get` printed Python's "None" for a JSON null, so every
`case … null)` a caller writes would miss. Now prints "null", as the
comment above it already promised for booleans.
The installer seeds the state instead of emitting the system.nix line,
because that line outranks the state and would strand the toggle.
V1 (V3 pending: HARDWARE-QUEUE). nix flake check --no-build, installer-
safety and option-docs all pass. Proved by eval/build, not assumed: a state
carrying autoLogin yields greetd initial_session {"user":"bernardo"}, the
template state (no autoLogin) yields none, and a hand-set null beats a state
that says otherwise; a state with only fingerprint.pam=true — nothing set by
hand — renders the hyprlock auth.fingerprint block; both new tools pass
bash -n and land in systemPackages (nomarchy-fingerprint only with a
reader); the patcher writes settings.greeter.autoLogin and no system.nix
line; and the get round trip prints null, so the menu reads "Auto-login
(off)" where it would have read "(on)".
The reader itself, the two rebuilds, and the reboot are hardware — queued.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
131 lines
5.5 KiB
Nix
131 lines
5.5 KiB
Nix
# 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;
|
||
};
|
||
};
|
||
};
|
||
};
|
||
}
|