dot

vizie's NixOS desktop: Hyprland, CachyOS kernel and gaming tweaks

586be2a

Drop greetd: autologin on tty1, start the session behind hyprlock

By Gustaf Granström

tty1 logs in automatically and its login shell starts Hyprland through uwsm.
A login-lock user unit runs hyprlock once Hyprland is up and is required by
graphical-session.target, so the bar, wallpaper, autostart apps and XDG
autostart only start after unlocking. If hyprlock fails or crashes, the
session shuts down and tty1 logs in again, locked. The VM gets a password to
unlock with.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

9 files changed +73 −241

modifiedMIGRATION.md
@@ -86,7 +86,8 @@
8686
8787 ## 4. On the new system
8888
89-- The login screen is the same gtkgreet over the blurred wallpaper.
89+- There is no login screen any more: tty1 logs you in and Hyprland starts behind
90+ hyprlock, so unlocking it is logging in. The desktop starts after you unlock.
9091 - Only what `restore-filter` lists came back: app data and logins, SSH keys,
9192 shell history, `~/git/dot`, Steam's settings and saves. Everything else
9293 (Downloads, the other repos, CachyOS leftovers) is on the backup drive, at
@@ -120,10 +121,6 @@
120121 installed, so kitty has been showing Noto Sans Mono. That's kept. Add
121122 `nerd-fonts.jetbrains-mono` to `fonts.packages` in `modules/nixos/desktop.nix`
122123 to get the font the config names.
123-- **The login screen opens a kitty terminal** behind gtkgreet
124- (`hl.exec_cmd("kitty")` in `config/greetd/hyprland.lua`), running as the
125- greeter user. If gtkgreet ever crashed, that shell would be on screen before
126- anyone logs in. If it's a leftover from debugging, delete that line.
127124 - **`~/.ssh/authorized_keys`** has a key labelled `chris@echoz.io`, which lets
128125 that key log in as you over SSH (port 22 is open, as before). It stays in your
129126 home folder.
modifiedREADME.md
@@ -2,8 +2,8 @@
22
33 NixOS configuration for `desktop`: the CachyOS setup from
44 [Eiziv/dotfiles]https://github.com/Eiziv/dotfiles, rewritten as a flake with
5-Home Manager. Hyprland (Lua config) under uwsm, the greetd/gtkgreet login screen,
6-the patched Waybar and the Eww control centre, the CachyOS kernel and the CachyOS
5+Home Manager. Hyprland (Lua config) under uwsm, logged in automatically and
6+started behind hyprlock, the patched Waybar and the Eww control centre, the CachyOS kernel and the CachyOS
77 system tweaks.
88
99 Moving from CachyOS: see [MIGRATION.md]MIGRATION.md. The install wipes the
@@ -19,7 +19,7 @@
1919 base.nix Nix, boot, locale, network, firewall, user
2020 cachyos.nix CachyOS kernel + cachyos-settings, file by file
2121 nvidia.nix RTX 3080 Ti, open kernel module
22- desktop.nix Hyprland, uwsm, greetd, audio, Bluetooth, fonts
22+ desktop.nix Hyprland, uwsm, autologin, audio, Bluetooth, fonts
2323 gaming.nix Steam, proton-cachyos, gamemode, gamescope, ...
2424 apps.nix desktop apps, Claude Cowork, OpenDeck udev, Flatpak
2525 vm.nix the test VM
deletedconfig/greetd/hyprland.lua
@@ -1,91 +1,0 @@
1--- gtkgreet runs as a full-screen layer (-l); blur the wallpaper behind it so the
2--- login screen looks like hyprlock (blur 5/2 + dim from style.css).
3-hl.layer_rule({
4- match = {
5- namespace = "gtk-layer-shell",
6- },
7- blur = true,
8- ignore_alpha = 0,
9-})
10-
11-hl.monitor({
12- output = "HDMI-A-2",
13- mode = "2560x1440@280",
14- position = "0x0",
15- scale = 1,
16- vrr = 0,
17-})
18-
19-hl.monitor({
20- output = "DP-1",
21- disabled = true,
22-})
23-
24--- Same cursor as the desktop session. The greeter user has no settings of its
25--- own, so without this Hyprland falls back to the default (Adwaita) cursor.
26--- gtkgreet's own window reads GNOME's cursor setting instead; that default comes
27--- from greetd/99_greeter-cursor.gschema.override in /usr/share/glib-2.0/schemas.
28-hl.env("XCURSOR_THEME", "BreezeX-Black")
29-hl.env("XCURSOR_SIZE", "28")
30-
31-hl.config({
32- misc = {
33- disable_hyprland_logo = true,
34- disable_splash_rendering = true,
35- -- Wallpaper's average colour (dimmed like style.css) instead of black,
36- -- shown for the moment before hyprpaper has drawn the wallpaper.
37- background_color = "rgb(0d1513)",
38- },
39- general = {
40- border_size = 2,
41- gaps_in = 5,
42- gaps_out = 17,
43- layout = "dwindle",
44- col = {
45- active_border = "rgb(aaffff)",
46- inactive_border = "rgba(00000000)",
47- },
48- },
49- decoration = {
50- rounding = 10,
51- blur = {
52- enabled = true,
53- size = 5,
54- passes = 2,
55- new_optimizations = true,
56- },
57- shadow = {
58- enabled = false,
59- render_power = 3,
60- range = 4,
61- color = "rgba(1a1a1aee)",
62- },
63- },
64- input = {
65- kb_layout = "no",
66- follow_mouse = 1,
67- sensitivity = 0,
68- accel_profile = "flat",
69- },
70- debug = {
71- disable_logs = false,
72- },
73-})
74-
75--- Login screen fades in over the wallpaper and out again when you log in.
76-hl.curve("easeOutQuint", { type = "bezier", points = { {0.23, 1}, {0.32, 1} } })
77-hl.curve("almostLinear", { type = "bezier", points = { {0.5, 0.5}, {0.75, 1} } })
78-hl.animation({ leaf = "layersIn", enabled = true, speed = 5, bezier = "easeOutQuint", style = "fade" })
79-hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 5, bezier = "almostLinear" })
80-hl.animation({ leaf = "layersOut", enabled = true, speed = 3, bezier = "almostLinear", style = "fade" })
81-hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 3, bezier = "almostLinear" })
82-
83-hl.on("hyprland.start", function()
84- hl.exec_cmd("hyprpaper -c /etc/greetd/hyprpaper.conf")
85- -- gtkgreet exits once the login succeeds: let its fade-out finish, stop
86- -- hyprpaper (it segfaults if the compositor disappears under it), then quit
87- -- this Hyprland cleanly so greetd starts the session right away. Without this
88- -- the greeter got killed and Hyprland/hyprpaper/portals crashed on the way out.
89- hl.exec_cmd("gtkgreet -l -c /etc/greetd/session.sh -s /etc/greetd/style.css; sleep 0.35; pkill -xf 'hyprpaper -c /etc/greetd/hyprpaper.conf'; sleep 0.1; hyprctl dispatch 'hl.dsp.exit()'")
90- hl.exec_cmd("kitty")
91-end)
deletedconfig/greetd/hyprpaper.conf
@@ -1,7 +1,0 @@
1-wallpaper {
2- monitor =
3- path = /etc/greetd/wallpapers
4- timeout = 30
5-}
6-
7-splash = false
deletedconfig/greetd/session.sh
@@ -1,11 +1,0 @@
1-#!/bin/sh
2-# Session started by the login screen (gtkgreet -c /etc/greetd/session.sh).
3-# The VT is briefly visible between the greeter quitting and Hyprland taking
4-# over the screen, so clear it, hide the text cursor, and keep uwsm's startup
5-# report off it. Hyprland itself runs as a systemd unit and logs to the journal.
6-#
7-# greetd starts this with a bare environment, so it goes through a login shell
8-# first: that loads NixOS's /etc/profile and Home Manager's session variables,
9-# which uwsm then hands to the systemd user session (and so to every service).
10-printf '\033[2J\033[3J\033[H\033[?25l'
11-exec /run/current-system/sw/bin/bash -lc 'exec uwsm start hyprland-uwsm.desktop >/dev/null 2>&1'
deletedconfig/greetd/style.css
@@ -1,64 +1,0 @@
1-/* greetd login screen (gtkgreet -l), styled like hyprlock (modules/home/desktop.nix):
2- full-screen layer, Hyprland blurs the wallpaper behind it (layer rule in
3- hyprland.lua), translucent black boxes, #eee text, rounding 10. */
4-* {
5- font-family: "NotoSans Nerd Font", "Noto Sans", sans-serif;
6- color: #eee;
7- text-shadow: none;
8- box-shadow: none;
9-}
10-
11-window {
12- background-color: rgba(0, 0, 0, 0.25);
13-}
14-
15-#clock {
16- font-weight: 300;
17- margin-bottom: 40px;
18-}
19-
20-/* The "Username:" / "Password:" prompt sits left of the box and can't be
21- moved above it, so hide it; the red "Login failed" label is not a first
22- child and stays visible. */
23-#body box > label:first-child {
24- color: transparent;
25- font-size: 1px;
26- min-width: 0;
27- margin: 0;
28- padding: 0;
29-}
30-
31-/* Content fades in just after Hyprland's layer fade (see hyprland.lua). */
32-@keyframes greet-in {
33- from { opacity: 0; }
34- to { opacity: 1; }
35-}
36-
37-#body {
38- animation: greet-in 500ms ease-out 150ms backwards;
39-}
40-
41-/* "Login failed" */
42-#body box > label:last-child:not(#clock) {
43- animation: greet-in 250ms ease-out;
44-}
45-
46-#input-field {
47- transition: background-color 200ms ease-out;
48- background: rgba(0, 0, 0, 0.2);
49- border: none;
50- border-radius: 10px;
51- min-height: 50px;
52- padding: 0 16px;
53- font-size: 15px;
54- caret-color: #eee;
55-}
56-
57-#input-field:focus {
58- background: rgba(0, 0, 0, 0.28);
59-}
60-
61-/* Session command and buttons: Enter logs in, like the lock screen. */
62-#command-selector, button {
63- opacity: 0;
64-}
@@ -250,9 +250,9 @@
250250 misc = {
251251 disable_hyprland_logo = true;
252252 disable_splash_rendering = true;
253- # The wallpaper's average colour instead of black, so the moment
254- # between the login screen and hyprpaper drawing the wallpaper doesn't
255- # flash black.
253+ # The wallpaper's average colour instead of black, so the moments
254+ # before hyprlock and after unlocking, before hyprpaper has drawn the
255+ # wallpaper, don't flash black.
256256 background_color = "rgb(121d1a)";
257257 };
258258 ecosystem.no_donation_nag = true;
@@ -280,8 +280,8 @@
280280
281281 # --- Lock screen, idle, wallpaper -----------------------------------------------
282282
283- # Styled to match the greetd login screen (config/greetd/style.css): same
284- # bamboo wallpaper, translucent black boxes, #eee text, rounding 10.
283+ # Also the login screen (login-lock below): the bamboo wallpaper, translucent
284+ # black boxes, #eee text, rounding 10.
285285 programs.hyprlock = {
286286 enable = true;
287287 # From the NixOS module (programs.hyprlock), which also sets up its PAM.
@@ -382,6 +382,44 @@
382382 };
383383 };
384384
385+ # The login screen. tty1 logs in by itself (modules/nixos/desktop.nix), so the
386+ # session starts locked: once Hyprland is up, hyprlock runs before
387+ # graphical-session.target, which requires it to exit successfully (unlocked),
388+ # so the bar, wallpaper, autostart apps and everything else in services.nix
389+ # only start after you unlock.
390+ #
391+ # If hyprlock fails (it couldn't lock, or crashed), the session ends rather
392+ # than carry on unlocked, and tty1 logs in again, locked. It only runs at
393+ # login: started on its own (a rebuild, systemctl) it does nothing.
394+ systemd.user.services.login-lock = {
395+ Unit = {
396+ Description = "Lock screen at login";
397+ # uwsm's waitenv finishes once Hyprland has put WAYLAND_DISPLAY in the
398+ # environment that this unit starts with.
399+ After = [
400+ "graphical-session-pre.target"
401+ "wayland-session-waitenv.service"
402+ ];
403+ Before = [ "graphical-session.target" ];
404+ OnFailure = [ "wayland-session-shutdown.target" ];
405+ OnFailureJobMode = "replace-irreversibly";
406+ X-RestartIfChanged = false;
407+ };
408+ Service = {
409+ Type = "oneshot";
410+ ExecCondition = "${pkgs.bash}/bin/bash -c '! ${pkgs.systemd}/bin/systemctl --user -q is-active graphical-session.target'";
411+ # Lock first and draw the wallpaper once it has loaded: Hyprland takes
412+ # input until the session is locked, and waiting for the wallpaper
413+ # roughly quadruples that time.
414+ ExecStart = "${lib.getExe pkgs.hyprlock} --immediate-render";
415+ TimeoutStartSec = "infinity";
416+ # With the compositor: session-graphical.slice only starts after
417+ # graphical-session.target.
418+ Slice = "session.slice";
419+ };
420+ Install.RequiredBy = [ "graphical-session.target" ];
421+ };
422+
385423 services.hypridle = {
386424 enable = true;
387425 settings = {
@@ -1,14 +1,9 @@
1-# Hyprland under uwsm, the greetd login screen, audio, Bluetooth, fonts and the
1+# Hyprland under uwsm, logging in on tty1, audio, Bluetooth, fonts and the
22 # system side of theming.
33 {
4- config,
5- lib,
64 pkgs,
75 ...
86 }:
9-let
10- greetdFiles = ../../config/greetd;
11-in
127 {
138 programs.hyprland = {
149 enable = true;
@@ -26,43 +21,28 @@
2621 extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
2722 };
2823
29- # --- Login screen -------------------------------------------------------------
24+ # --- Login --------------------------------------------------------------------
3025
31- # gtkgreet in a small Hyprland of its own, blurred over the wallpaper. Same
32- # files as /etc/greetd on CachyOS; see config/greetd.
33- services.greetd = {
34- enable = true;
35- settings = {
36- terminal.vt = 1;
37- default_session = {
38- command = "${config.programs.hyprland.package}/bin/start-hyprland -- --config /etc/greetd/hyprland.lua";
39- user = "greeter";
40- };
41- };
42- };
43- environment.etc = {
44- "greetd/hyprland.lua".source = "${greetdFiles}/hyprland.lua";
45- "greetd/hyprpaper.conf".source = "${greetdFiles}/hyprpaper.conf";
46- "greetd/style.css".source = "${greetdFiles}/style.css";
47- "greetd/session.sh".source = "${greetdFiles}/session.sh";
48- "greetd/wallpapers/wallpaper.jpg".source = ../../config/hypr/wallpaper.jpg;
49- };
26+ # No display manager: tty1 logs you in automatically and its login shell starts
27+ # Hyprland. The session comes up locked, though: hyprlock runs first and the
28+ # rest of the desktop (graphical-session.target) waits until it is unlocked;
29+ # see login-lock in modules/home/desktop.nix.
30+ services.getty.autologinUser = "vizie";
31+
32+ # In /etc/profile and /etc/zprofile, after the session variables. The tty test
33+ # comes first so that uwsm's environment preloader, which reads the profile
34+ # too, skips this without running uwsm. The VT is briefly visible before
35+ # Hyprland takes over the screen, so clear it, hide the text cursor, and keep
36+ # uwsm's startup report off it; Hyprland logs to the journal.
37+ environment.loginShellInit = ''
38+ if [ "$(tty)" = /dev/tty1 ] && uwsm check may-start -q; then
39+ printf '\033[2J\033[3J\033[H\033[?25l'
40+ exec uwsm start hyprland-uwsm.desktop >/dev/null 2>&1
41+ fi
42+ '';
5043
51- # The greeter user has no settings of its own; these system-wide GSettings
52- # defaults give gtkgreet the desktop's cursor. They replace
53- # 99_greeter-cursor.gschema.override from CachyOS.
54- programs.dconf = {
55- enable = true;
56- profiles.user.databases = [
57- {
58- settings."org/gnome/desktop/interface" = {
59- cursor-theme = "BreezeX-Black";
60- cursor-size = lib.gvariant.mkInt32 28;
61- color-scheme = "prefer-dark";
62- };
63- }
64- ];
65- };
44+ # Home Manager's dconf.settings (theming.nix, services.nix) need it.
45+ programs.dconf.enable = true;
6646
6747 # --- Audio, Bluetooth, devices --------------------------------------------------
6848
@@ -125,12 +105,6 @@
125105 };
126106
127107 environment.systemPackages = with pkgs; [
128- # The greeter runs these from /etc/greetd/hyprland.lua.
129- gtkgreet
130- hyprpaper
131- kitty
132- procps
133-
134108 breezex-cursor
135109 papirus-icon-theme
136110 kdePackages.breeze-icons
@@ -2,8 +2,8 @@
22 # nixos-rebuild build-vm --flake .#desktop (or ./vm.sh)
33 # It shares the host's /nix/store, so it builds in seconds once the real
44 # system has. Only what can't work in a VM is changed: no NVIDIA, a virtual GPU,
5-# the configs copied in rather than linked to your checkout, and an automatic
6-# first login (log out, Super+Delete, to see the real login screen).
5+# the configs copied in rather than linked to your checkout, and a password to
6+# get past the lock screen at login.
77 { lib, ... }:
88 {
99 virtualisation.vmVariant = {
@@ -50,11 +50,7 @@
5050 dot.nvidia.enable = false;
5151 home-manager.users.vizie.dot.liveConfig = lib.mkForce false;
5252
53- # greetd's one-time initial session: straight into the desktop on boot; the
54- # login screen comes up after you log out.
55- services.greetd.settings.initial_session = {
56- command = "/etc/greetd/session.sh";
57- user = "vizie";
58- };
53+ # The VM's user has no password of its own to unlock the session with.
54+ users.users.vizie.initialPassword = "vizie";
5955 };
6056 }