All checks were successful
Check / eval (push) Successful in 3m16s
BACKLOG #116. `nomarchy.settings` is declared in exactly one place —
modules/home/options.nix:412, the Home Manager side. On NixOS the attribute
does not exist, and `or <fallback>` swallows the missing-attribute error, so
four options that "defaulted from the state" had silently been their fallback
on every machine ever built.
The item said three options, and called them benign. Both halves were wrong,
and re-grepping rather than trusting the account is what found it:
* There were four. The original enumeration read options.nix instead of
modules/nixos/ and missed services.nix's printing.enable — the same
mistake in miniature as the bug it was filing.
* Two were live user-facing bugs. Control Center is shipped
(default.nix:337) and reachable from the menu; its Bluetooth and Printing
toggles wrote settings.{bluetooth,printing}.enable and printed "requires
rebuild", and the rebuild changed nothing. They had never worked.
The fix is one shape, now uniform: the option declares a STATIC default, and
the implementing module reads the state via theme-state-read.nix (fails closed
on bad JSON, unlike greeter.nix's raw fromJSON — also moved onto the reader
here) and mkDefaults it behind `mkIf (state != null)`. An absent key leaves the
option default as the single source of the fallback; a hand-set system.nix
value still pins it. batteryChargeLimit gets no bridge and loses its dead read:
power.nix's oneshot already reads that key with jq at RUNTIME and prefers it
over the baked value, which is why that menu worked all along.
V2. The bug is proved real before/after on the same flipped state: BEFORE,
bluetooth stays true and printing stays false; AFTER, both flip, and a hand-set
value still outranks the state. Nothing in a build fails when a bridge dies, so
the guards are the point — checks.state-bridges asserts 11 eval cases, and
checks.printing-from-state boots a VM whose only input is the state file and
waits for a running cups.service. The guard was itself proved to fail:
re-breaking the bluetooth bridge makes it throw, naming both assertions. A
check that passes whether or not the property holds is worse than no check
(625b7e3). flake check, option-docs, template-sot, downstream-template-*,
installer-safety, hardware-toggles and battery-charge-limit all pass.
No V3: the mechanism is fully proved headlessly. Design record in ROADMAP §
NixOS-side state bridges (#116); new #117 (PROPOSED) for the control-center
toggles still leaving the rebuild to the user.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
134 lines
5.7 KiB
Nix
134 lines
5.7 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
|
||
'';
|
||
|
||
# Fails closed with an actionable message via theme-state-read.nix, like
|
||
# every other stateFile consumer — a raw fromJSON here would bury a bad
|
||
# state file under a Nix stack pointing at greeter.nix.
|
||
state =
|
||
if cfg.stateFile != null
|
||
then import ../theme-state-read.nix { inherit lib; } 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;
|
||
};
|
||
};
|
||
};
|
||
};
|
||
}
|