From 00c89f23f0b1387a66257bbe86f51dfddb82b246 Mon Sep 17 00:00:00 2001 From: alvinlollo Date: Tue, 13 Oct 2026 19:33:57 +1100 Subject: [PATCH] feat(installer): Skip login manager when unl0kr handles the passphrase unl0kr already prompts for the LUKS passphrase on screen, so a login manager on top of it would be a second prompt. The login screen now only collects a fallback (none/greetd/sddm/gdm) when unl0kr is selected; resolve_login_manager applies it only when install_unl0kr falls back to the systemd initramfs, and the summary shows both states. Also ships /etc/unl0kr.conf (breezy-dark) plus the empty /usr/share/unl0kr/unl0kr.conf the package omits so unl0kr stops warning about an unparseable config, and pins Hyprland in the user's AccountsService record so gdm autologin does not fall back to gnome.desktop. --- AGENTS.md | 2 +- airootfs/usr/local/bin/alvin-install | 99 +++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 857ed54..6d9b12e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,7 +74,7 @@ The airootfs overlay ships it as `/usr/local/bin/alvin-install` (mode `0755` for locale, login manager, sshd, summary) then a fixed order of `install_*` steps (partition → LUKS → format/mount → pacstrap → fstab → repos → timezone → locale → hostname → network → sshd → user → git/SSH → end-4 dots → mkinitcpio → bootloader → - TPM/unl0kr → login manager → autologin → update (reflector + keyrings + `pacman -Syu`, warn-only) → cleanup). Screens and install steps are + TPM/unl0kr → login manager (none if unl0kr succeeded; with unl0kr selected the login screen only asks for a fallback) → autologin → update (reflector + keyrings + `pacman -Syu`, warn-only) → cleanup). Screens and install steps are independent — edit one without touching the other. An `EXIT` trap unmounts `$TARGET` and closes `cryptroot` on any exit. - **Package list**: `install_pacstrap` builds the *installed* system's list from the medium's diff --git a/airootfs/usr/local/bin/alvin-install b/airootfs/usr/local/bin/alvin-install index 508ba8d..9988d1e 100755 --- a/airootfs/usr/local/bin/alvin-install +++ b/airootfs/usr/local/bin/alvin-install @@ -31,6 +31,9 @@ GH_USERNAME="" UNL0KR="false" TPM_STATUS="" LOGIN_MANAGER="none" +# true when unl0kr was picked: no login manager is installed if unl0kr +# succeeds, and LOGIN_MANAGER only holds the fallback for when it doesn't. +LOGIN_MANAGER_SKIP="false" AUTOLOGIN="" # SSHD is the choice made on the sshd screen; SSHD_APPLIED is only flipped once # the target's sshd.service has actually been enabled, so the final banner can @@ -527,6 +530,11 @@ screen_locale() { } screen_login_manager() { + if [[ "$LUKS" == "true" && "$UNL0KR" == "true" ]]; then + screen_unl0kr_login + return + fi + LOGIN_MANAGER_SKIP="false" clear header "Login Manager" echo "" @@ -543,6 +551,33 @@ screen_login_manager() { log "Login manager: $LOGIN_MANAGER" } +# unl0kr already asks for the passphrase on screen at boot, so a login +# manager on top would be a second prompt. Skip it, but collect a fallback +# now: install_unl0kr can still fall back to the systemd initramfs, and the +# installer must not stop to ask mid-install. +screen_unl0kr_login() { + LOGIN_MANAGER_SKIP="true" + clear + header "Login Manager (unl0kr)" + echo "" + gum style --foreground 15 \ + "unl0kr is selected, so no login manager will be installed:" \ + "after the LUKS passphrase is entered with unl0kr, the system logs in on" \ + "tty1 and Hyprland starts automatically (autologin is asked during install)." \ + "" \ + "If unl0kr fails to install, the installer falls back to the standard" \ + "passphrase prompt. Pick the login manager to use in that case:" \ + "" \ + " none - plain tty login, Hyprland starts automatically" \ + " greetd - lightweight TUI greeter (tuigreet)" \ + " sddm - Qt display manager (already installed)" \ + " gdm - GNOME display manager (large download)" + echo "" + LOGIN_MANAGER=$(gum choose --header "+ Fallback login manager (Enter = none)" \ + "none" "greetd" "sddm" "gdm") || die "No fallback login manager selected." + log "Login manager: skipped for unl0kr (fallback: $LOGIN_MANAGER)" +} + screen_sshd() { clear header "SSH Server" @@ -592,6 +627,11 @@ screen_summary() { ssh_line="gh:${GH_USERNAME}" fi + local login_line="${LOGIN_MANAGER}" + if [[ "$LOGIN_MANAGER_SKIP" == "true" ]]; then + login_line="none - unl0kr (fallback: ${LOGIN_MANAGER})" + fi + gum style --foreground 15 --border normal --border-foreground 8 \ --padding "0 2" --width 60 \ "Disk: ${DISK}" \ @@ -603,7 +643,7 @@ screen_summary() { "Keymap: ${KEYMAP}" \ "Locale: ${LOCALE}" \ "Timezone: ${TIMEZONE}" \ - "Login mgr: ${LOGIN_MANAGER}" \ + "Login mgr: ${login_line}" \ "SSH server: $([[ "$SSHD" == "true" ]] && echo enabled || echo skipped)" \ "SSH keys: ${ssh_line}" \ "Git: ${git_line}" @@ -1715,6 +1755,34 @@ install_unl0kr() { page_log_tail "unl0kr hook file output (q to quit)" 40 || true } + # unl0kr parses /usr/share/unl0kr/unl0kr.conf first; the package doesn't + # ship it, so inih fails to open it and unl0kr prints "Ignoring invalid + # config file" on every boot. An empty file parses cleanly - FILES+= puts + # it in the image (the hook only adds /etc/unl0kr.conf). Our own + # /etc/unl0kr.conf pins the dark theme instead of trusting the PKGBUILD's + # sed on the upstream (light) default. + mkdir -p "${TARGET}/usr/share/unl0kr" + : >"${TARGET}/usr/share/unl0kr/unl0kr.conf" + cat >"${TARGET}/etc/unl0kr.conf" <<'UNL0KR' +[general] +animations=true + +[keyboard] +autohide=false +layout=us +popovers=true + +[textarea] +obscured=true + +[theme] +default=breezy-dark +alternate=breezy-light +UNL0KR + echo 'FILES+=(/usr/share/unl0kr/unl0kr.conf)' \ + >>"${TARGET}/etc/mkinitcpio.conf.d/alvin.conf" + log "unl0kr: wrote /etc/unl0kr.conf (breezy-dark) + empty /usr/share/unl0kr/unl0kr.conf" + # Touchscreen/input stack for the initramfs: mkinitcpio fails the build # on a MODULES entry it cannot resolve, and -P applies the list to every # preset - so a module may enter MODULES only if EVERY installed kernel @@ -1904,6 +1972,21 @@ FISH log "tty profile: ${profile} (exec Hyprland on VT1)" } +# Runs after install_unl0kr, which resets UNL0KR to false when it falls +# back to the systemd initramfs - so UNL0KR still being true here means +# unl0kr really is in the image and no login manager is wanted. +resolve_login_manager() { + [[ "$LOGIN_MANAGER_SKIP" == "true" ]] || return 0 + if [[ "$UNL0KR" == "true" ]]; then + LOGIN_MANAGER="none" + info "unl0kr is installed - skipping the login manager (tty1 + Hyprland)." + log "Login manager: none (unl0kr installed)" + else + warn "unl0kr fell back - using the fallback login manager: ${LOGIN_MANAGER}." + log "Login manager: fallback ${LOGIN_MANAGER} (unl0kr not installed)" + fi +} + install_login_manager() { info "Configuring login manager: ${LOGIN_MANAGER}..." @@ -2009,6 +2092,19 @@ TOML return fi ensure_hyprland_session + # gdm (autologin included) starts the session stored in the user's + # AccountsService record; without one it picks gnome.desktop, which + # the gdm install drags in. Pin Hyprland. + local as_dir="${TARGET}/var/lib/AccountsService/users" + mkdir -p "$as_dir" + cat >"${as_dir}/${USERNAME}" <<'ACCOUNT' +[User] +Session=hyprland +SessionType=wayland +SystemAccount=false +ACCOUNT + chmod 600 "${as_dir}/${USERNAME}" + log "gdm: AccountsService session pinned to hyprland" if run_logged "Could not enable gdm.service" \ arch-chroot "$TARGET" systemctl enable gdm.service; then run_logged "Could not disable getty@tty1.service" \ @@ -2286,6 +2382,7 @@ main() { install_bootloader install_tpm install_unl0kr + resolve_login_manager install_login_manager install_autologin install_update