feat: regreet and swaylock from nixpkgs, autologin after LUKS

greetd.nix configures services.displayManager.regreet and keeps niri as
the greeter compositor, so niri-greeter.kdl stays the shared file that
carries the German keyboard layout. The moongreet input, its package and
pkgs/moongreet.nix are gone.

extraCss goes through builtins.readFile: the module only recognises a
direct /nix/store child as a path, so passing the file path as a string
wrote the path itself into /etc/greetd/regreet.css.

desktop.nix installs swaylock, seeds ~/.config/swaylock/config with the
wallpaper rewritten to the store path, and now owns the moonlock PAM
stack. The Colloid overrides moved to flake.nix so the greeter and the
desktop share one derivation.

initial_session is set per host because the accounts differ. greetd's
restart option flips itself off, verified.

flake.lock still pins moonarch at the pushed revision; it needs
`nix flake update moonarch` once the moonarch commit is pushed.
This commit is contained in:
2026-08-14 14:32:43 +02:00
parent 0e0c0d5b92
commit f5965b2328
9 changed files with 189 additions and 131 deletions
+48
View File
@@ -1,5 +1,53 @@
# Decisions
## 2026-08-14 regreet and swaylock from nixpkgs, autologin after LUKS
- **Who**: Dominik, ClaudeCode
- **Why**: moongreet and moonlock were dropped as self-maintained projects; the
reasoning is in `moonarch/DECISIONS.md` for the same date. Here the change is
mostly subtraction: two flake inputs, two build recipes and a generated config
disappear in favour of a nixpkgs module and a nixpkgs package. Since LUKS
already asks for a passphrase at boot, the greeter was also asking for a
second one for no gain, so autologin replaces it on both hosts.
- **Tradeoffs**: (1) **Greeter host** — the regreet module sets
`services.greetd.settings.default_session.command` only as `mkDefault`
(`dbus-run-session cage -- regreet`), so the existing unconditional niri line
keeps winning. Taking cage would have meant one Wayland compositor fewer in
the greeter path, but `niri-greeter.kdl` is where `layout "de"` and `numlock`
live, and nothing in nixpkgs 26.11 exports `XKB_DEFAULT_LAYOUT` from
`services.xserver.xkb.layout` — verified. A greeter on a US layout rejects
every password with an affected character, so niri stays and Arch and NixOS
keep sharing that one file. (2) **regreet.toml** — the module writes
`/etc/greetd/regreet.toml` itself, so the committed file from
`defaults/etc/greetd/` is deliberately *not* deployed verbatim; declaring the
same path in `environment.etc` would collide. The values are duplicated into
module options, which is the price for the wallpaper store path. (3)
**swaylock config** — nixpkgs passes only `--prefix` to meson, so swaylock's
`SYSCONFDIR` sits inside the store and `/etc/swaylock/config` is never read.
The config is therefore seeded into `$HOME` with the same `C` tmpfiles rule
waypaper uses, which also means later user edits survive but updates do not
propagate. (4) **Colloid overrides** — desktop and greeter need the same
derivation, and two module files cannot share a `let`. Moved into `flake.nix`
and passed as `_module.args.moonarchThemes` rather than duplicating the
override, which would drift.
- **How**: `moongreet` input, `moonarchPackages.moongreet` and
`pkgs/moongreet.nix` removed. `modules/greetd.nix` now configures
`services.displayManager.regreet` (background, GTK, commands, theme, icon
theme, cursor theme, font, `extraCss` pointing at the CSS in the moonarch
input) and keeps the niri `default_session`. `modules/desktop.nix` gains
`swaylock`, the seeded `~/.config/swaylock/config` and — moved out of
`greetd.nix`, next to the package it belongs to — the moonlock PAM stack.
`initial_session` is set per host, because the accounts are named differently.
- **Remaining exception**: `moonlock` stays packaged and installed.
`moonset/src/power.rs` spawns it by name and has no `lock_command` option, so
its Lock action would otherwise fail. Idle and lid-close already go through
swaylock. Two lockers coexist until moonset is released with that call
changed; then the input, the recipe, the PAM stack and
`environment.etc."moonlock/moonlock.toml"` all go.
- **Consequence**: With autologin, the greeter no longer forces an account to
have a password, but swaylock still needs one — `passwd` after installing
stays mandatory, and `hosts/testvm` has no password declared at all.
## 2026-08-11 Disk layout via disko after all, as a shared module
- **Who**: Dominik, ClaudeCode
+16 -13
View File
@@ -1,12 +1,13 @@
# moonix
NixOS configuration for a scrollable-tiling Wayland desktop: niri as the
compositor, Quickshell as the bar and launcher, moongreet as the login greeter,
moonlock as the screen locker, Catppuccin Mocha throughout.
compositor, Quickshell as the bar and launcher, regreet as the login greeter,
swaylock as the screen locker, Catppuccin Mocha throughout.
Ships Nix packages for the components that are not in nixpkgs — moonlock,
moongreet, moonset, stasis, sweet-cursors — and NixOS modules that assemble
them into a working session.
Ships Nix packages for the components that are not in nixpkgs — moonset, stasis,
sweet-cursors — and NixOS modules that assemble them into a working session.
`moonlock` is still packaged here for one reason: moonset's Lock action invokes
it by name. It goes with the moonset release that changes that.
The XDG configuration files (niri, Quickshell, foot, GTK, Qt) come from the
`moonarch` repository, which is consumed as a flake input. One set of config
@@ -17,8 +18,7 @@ files therefore stays valid on both NixOS and Arch.
```
flake.nix Inputs and outputs
pkgs/ Build recipes
moonlock.nix screen locker (GTK4, PAM, fingerprint)
moongreet.nix greetd greeter
moonlock.nix screen locker, retired but still called by moonset
moonset.nix session power menu
stasis.nix idle manager
sweet-cursors.nix cursor theme
@@ -26,7 +26,7 @@ pkgs/ Build recipes
modules/
disk.nix partitioning: GPT, optional LUKS2, btrfs subvolumes
desktop.nix /etc deployment, package set, theme
greetd.nix greetd + moongreet
greetd.nix greetd + regreet, autologin per host
services.nix systemd system and user services
hosts/
testvm/ QEMU test VM
@@ -78,8 +78,9 @@ the NixOS ISO and run one command.
The LUKS passphrase is asked for twice while formatting. The repo is fetched
from the flake reference, so the ISO needs no `git` and nothing is cloned.
2. Set the user password. Nothing has created one yet, and greetd is the only
way in:
2. Set the user password. Nothing has created one yet. Autologin gets past the
greeter without it, but swaylock authenticates through pam_unix and cannot
unlock a screen for an account that has no password:
```bash
sudo nixos-enter --root /mnt -c 'passwd dkressler'
@@ -149,12 +150,14 @@ nix flake update moonlock
## Configuration files
Config files are deployed unchanged wherever possible. Three are generated
instead, because they contain a path that does not exist under Nix:
Config files are deployed unchanged wherever possible. These are generated
instead, because they contain a path that does not exist under Nix, or because a
NixOS module owns the destination:
| File | Reason |
|---|---|
| `moongreet.toml` | wallpaper path, rewritten to the store path |
| `greetd/regreet.toml`, `regreet.css` | written by `services.displayManager.regreet` from module options; the wallpaper points at the store path |
| `swaylock/config` | wallpaper path, rewritten and seeded into `$HOME` — swaylock's sysconfdir is inside the store, so a system-wide copy is never read |
| `waypaper/config.ini` | wallpaper path, rewritten and seeded into `$HOME` |
| fontconfig defaults | `/etc/fonts/conf.d` is owned by the NixOS module |
Generated
-18
View File
@@ -37,23 +37,6 @@
"url": "https://gitea.moonarch.de/nevaforget/moonarch.git"
}
},
"moongreet": {
"flake": false,
"locked": {
"lastModified": 1786110932,
"narHash": "sha256-0idBtqnIAGbRTXqh/EBS0ZxUMf4EgpUPtNQoJ/ogW7U=",
"ref": "refs/tags/v0.10.2",
"rev": "8dd90b016fc91adbe5d6b6f3fb13d470b8cdd5cf",
"revCount": 76,
"type": "git",
"url": "https://gitea.moonarch.de/nevaforget/greetd-moongreet.git"
},
"original": {
"ref": "refs/tags/v0.10.2",
"type": "git",
"url": "https://gitea.moonarch.de/nevaforget/greetd-moongreet.git"
}
},
"moonlock": {
"flake": false,
"locked": {
@@ -139,7 +122,6 @@
"inputs": {
"disko": "disko",
"moonarch": "moonarch",
"moongreet": "moongreet",
"moonlock": "moonlock",
"moonset": "moonset",
"nixos-hardware": "nixos-hardware",
+16 -10
View File
@@ -24,11 +24,6 @@
url = "git+https://gitea.moonarch.de/nevaforget/moonlock.git?ref=refs/tags/v0.6.21";
flake = false;
};
# Note the repo name: the project is moongreet, the repo is greetd-moongreet.
moongreet = {
url = "git+https://gitea.moonarch.de/nevaforget/greetd-moongreet.git?ref=refs/tags/v0.10.2";
flake = false;
};
moonset = {
url = "git+https://gitea.moonarch.de/nevaforget/moonset.git?ref=refs/tags/v0.9.2";
flake = false;
@@ -55,11 +50,6 @@
version = "0.6.21";
};
moongreet = pkgs.callPackage ./pkgs/moongreet.nix {
src = inputs.moongreet;
version = "0.10.2";
};
moonset = pkgs.callPackage ./pkgs/moonset.nix {
src = inputs.moonset;
version = "0.9.2";
@@ -79,6 +69,21 @@
src = inputs.moonarch;
};
};
# Colloid overrides, used by the desktop session and by the greeter.
# Defined once so the two cannot drift apart.
themePackages = {
gtk = pkgs.colloid-gtk-theme.override {
themeVariants = [ "grey" ];
colorVariants = [ "dark" ];
tweaks = [ "catppuccin" ];
};
icons = pkgs.colloid-icon-theme.override {
schemeVariants = [ "catppuccin" ];
colorVariants = [ "grey" ];
};
};
in
{
packages.${system} = moonarchPackages;
@@ -97,6 +102,7 @@
_module.args = {
moonarchSrc = inputs.moonarch;
moonarchPkgs = moonarchPackages;
moonarchThemes = themePackages;
};
};
+12
View File
@@ -39,6 +39,18 @@
shell = pkgs.zsh;
};
# LUKS already asked for a passphrase at boot, so the greeter would only ask
# for a second one. initial_session runs once per boot — the greeter comes
# back on logout, which is also where a session gets switched. greetd's
# restart option flips itself off when this is set.
#
# The account still needs a password: swaylock authenticates through
# pam_unix, and without a /etc/shadow entry the screen cannot be unlocked.
services.greetd.settings.initial_session = {
command = "${pkgs.niri}/bin/niri-session";
user = "kresdo";
};
# Development access from the host via the QEMU port forward on 127.0.0.1:2222.
services.openssh.enable = true;
+12 -2
View File
@@ -41,8 +41,9 @@
extraGroups = [ "networkmanager" "wheel" "video" "input" "plugdev" "docker" ];
shell = pkgs.zsh;
# No password is declared here, so a freshly installed system has no
# /etc/shadow entry for this account and greetd cannot let anyone in. The
# password is set once after installing, see README.
# /etc/shadow entry for this account. Autologin gets past the greeter
# without one, but swaylock then cannot unlock the screen, so the password
# is still set once after installing, see README.
#
# To make it declarative instead, put the output of `mkpasswd -m sha-512`
# in initialHashedPassword. That commits a hash to the repository, which is
@@ -50,6 +51,15 @@
# initialHashedPassword = "$6$...";
};
# LUKS already asked for a passphrase at boot, so the greeter would only ask
# for a second one. initial_session runs once per boot — the greeter comes
# back on logout, which is also where a session gets switched. greetd's
# restart option flips itself off when this is set.
services.greetd.settings.initial_session = {
command = "${pkgs.niri}/bin/niri-session";
user = "dkressler";
};
# Snapshots of root and home. snap-pac has no counterpart here: NixOS keeps
# its own generations, so pre/post package snapshots are redundant.
services.snapper = {
+26 -12
View File
@@ -1,7 +1,7 @@
# ABOUTME: Deploys the XDG configs from the moonarch repo and the package set.
# ABOUTME: Niri, Quickshell, terminal, theming, portals.
{ pkgs, lib, moonarchSrc, moonarchPkgs, ... }:
{ pkgs, lib, moonarchSrc, moonarchPkgs, moonarchThemes, ... }:
let
xdg = "${moonarchSrc}/defaults/xdg";
@@ -23,6 +23,15 @@ let
substitute ${xdg}/waypaper/config.ini "$out" \
--replace-fail /usr/share/moonarch/wallpaper.jpg ${wallpaper}
'';
# Same treatment for swaylock's wallpaper path. swaylock searches
# ~/.swaylock/config, $XDG_CONFIG_HOME/swaylock/config and
# SYSCONFDIR/swaylock/config — and its sysconfdir is inside the store here,
# so the home copy seeded below is the only one it ever reads.
swaylockConfig = pkgs.runCommand "swaylock-config" { } ''
substitute ${xdg}/swaylock/config "$out" \
--replace-fail /usr/share/moonarch/wallpaper.jpg ${wallpaper}
'';
in
{
# --- XDG configs -> /etc/xdg/ ---
@@ -148,11 +157,16 @@ in
environment.systemPackages = with pkgs; [
# Own programs
moonarchPkgs.moonlock
moonarchPkgs.moonset
moonarchPkgs.sweet-cursors
moonarchPkgs.stasis
moonarchPkgs.moonarch-scripts
# Only still installed because moonset's Lock action invokes moonlock by
# name and has no lock_command option. Idle and lid-close use swaylock.
moonarchPkgs.moonlock
# Session lock
swaylock
# Compositor extras
xwayland-satellite
@@ -191,16 +205,9 @@ in
udisks2
ntfs3g
# Theming
(colloid-gtk-theme.override {
themeVariants = [ "grey" ];
colorVariants = [ "dark" ];
tweaks = [ "catppuccin" ];
})
(colloid-icon-theme.override {
schemeVariants = [ "catppuccin" ];
colorVariants = [ "grey" ];
})
# Theming. Same derivations the greeter uses, see flake.nix.
moonarchThemes.gtk
moonarchThemes.icons
libsForQt5.qtstyleplugin-kvantum
qt6Packages.qtstyleplugin-kvantum
qt6Packages.qt6ct
@@ -235,5 +242,12 @@ in
# edits survive. This is what post-install.sh does imperatively on Arch.
systemd.user.tmpfiles.rules = [
"C %h/.config/waypaper/config.ini 0644 - - - ${waypaperConfig}"
"C %h/.config/swaylock/config 0644 - - - ${swaylockConfig}"
];
# PAM stack for moonlock, shipped by the package but owned by the system.
# swaylock needs none of this: programs.niri.enable brings
# security.pam.services.swaylock with it. Goes away with moonlock.
security.pam.services.moonlock.text =
builtins.readFile "${moonarchPkgs.moonlock}/share/moonlock/moonlock-pam";
}
+59 -43
View File
@@ -1,61 +1,77 @@
# ABOUTME: Sets up greetd with moongreet as the login greeter.
# ABOUTME: Sets up greetd with regreet as the login greeter.
# ABOUTME: Counterpart to defaults/etc/greetd/ in the moonarch repo.
{ pkgs, lib, moonarchSrc, moonarchPkgs, ... }:
{ pkgs, lib, moonarchSrc, moonarchPkgs, moonarchThemes, ... }:
let
wallpaper = "${moonarchSrc}/defaults/backgrounds/wallpaper.jpg";
# moongreet.toml cannot be copied verbatim: the upstream file points at
# /usr/share/moonarch/wallpaper.jpg, which does not exist under Nix.
# Everything else matches defaults/etc/moongreet/moongreet.toml.
moongreetConfig = pkgs.writeText "moongreet.toml" ''
[appearance]
background = "${wallpaper}"
cursor-theme = "Sweet-cursors"
cursor-size = 24
'';
greeterNiriConfig = "${moonarchSrc}/defaults/etc/greetd/niri-greeter.kdl";
in
{
environment.etc = {
"greetd/niri-greeter.kdl".source = greeterNiriConfig;
"moongreet/moongreet.toml".source = moongreetConfig;
};
environment.etc."greetd/niri-greeter.kdl".source = greeterNiriConfig;
# moongreet caches the last user and session under /var/cache/moongreet.
# Without the directory it logs a permission error on every start.
systemd.tmpfiles.rules = [
"d /var/cache/moongreet 0755 greeter greeter -"
"d /var/cache/moongreet/last-session 0755 greeter greeter -"
];
# /etc/greetd/regreet.toml and regreet.css are written by the module, so
# defaults/etc/greetd/regreet.toml is not deployed verbatim: its wallpaper
# path points at /usr/share/moonarch/, which does not exist here. Every other
# value matches that file. Declaring those two paths in environment.etc would
# collide with the module.
services.displayManager.regreet = {
enable = true;
settings = {
background = {
path = wallpaper;
fit = "Cover";
};
GTK.application_prefer_dark_theme = true;
commands = {
reboot = [ "systemctl" "reboot" ];
poweroff = [ "systemctl" "poweroff" ];
};
};
# The rest of the GTK table is generated from these options.
theme = {
package = moonarchThemes.gtk;
name = "Colloid-Grey-Dark-Catppuccin";
};
iconTheme = {
package = moonarchThemes.icons;
name = "Colloid-Grey-Catppuccin-Dark";
};
cursorTheme = {
package = moonarchPkgs.sweet-cursors;
name = "Sweet-cursors";
};
font = {
package = pkgs.nerd-fonts.ubuntu-sans;
name = "UbuntuSans Nerd Font";
size = 12;
};
# readFile, not the path: the module only treats a direct /nix/store child
# as a path (lib.isStorePath), so passing this file's path as a string would
# write the path itself into /etc/greetd/regreet.css.
extraCss = builtins.readFile "${moonarchSrc}/defaults/etc/greetd/regreet.css";
};
services.greetd = {
enable = true;
settings = {
terminal.vt = 1;
default_session = {
# moongreet needs a Wayland compositor; niri provides it.
command = "${lib.getExe pkgs.niri} -c /etc/greetd/niri-greeter.kdl";
user = "greeter";
};
settings.default_session = {
# regreet needs a Wayland compositor. niri instead of the module's cage
# default: niri-greeter.kdl carries the German keyboard layout, numlock
# and the cursor theme, and cage would come up with none of them.
command = "${lib.getExe pkgs.niri} -c /etc/greetd/niri-greeter.kdl";
user = "greeter";
};
};
# greetd is WantedBy=graphical.target. An installation without a desktop
# boots into multi-user.target, where the unit never starts.
systemd.defaultUnit = "graphical.target";
# moongreet walks XDG_DATA_DIRS for wayland-sessions/ and xsessions/.
# NixOS does not link those directories into the system profile by default.
# regreet walks XDG_DATA_DIRS for wayland-sessions/ and xsessions/.
environment.pathsToLink = [ "/share/wayland-sessions" "/share/xsessions" ];
# The greeter session spawns `moongreet` by name from niri-greeter.kdl,
# so it has to resolve in PATH.
environment.systemPackages = [ moonarchPkgs.moongreet ];
# PAM stack for moonlock, shipped by the package but owned by the system.
security.pam.services.moonlock.text =
builtins.readFile "${moonarchPkgs.moonlock}/share/moonlock/moonlock-pam";
}
-33
View File
@@ -1,33 +0,0 @@
# ABOUTME: Nix build recipe for moongreet, the greetd greeter for Wayland.
# ABOUTME: Counterpart to moonarch-pkgbuilds/moongreet/PKGBUILD.
{ lib
, rustPlatform
, pkg-config
, wrapGAppsHook4
, glib
, gtk4
, gtk4-layer-shell
, src
, version
}:
rustPlatform.buildRustPackage {
pname = "moongreet";
inherit version src;
cargoLock.lockFile = "${src}/Cargo.lock";
# glib provides glib-compile-resources for the GResource bundle in build.rs.
nativeBuildInputs = [ pkg-config wrapGAppsHook4 glib ];
buildInputs = [ gtk4 gtk4-layer-shell ];
meta = {
description = "greetd greeter for Wayland with GTK4 and Layer Shell";
homepage = "https://gitea.moonarch.de/nevaforget/moongreet";
license = lib.licenses.mit;
mainProgram = "moongreet";
platforms = lib.platforms.linux;
};
}