feat(greeter): switch to greetd+ReGreet (Wayland), fix FIDO/scaling/wallpaper

Replace the Xorg lightdm-gtk-greeter with a wlroots-native greetd + ReGreet
greeter (inside a cage kiosk), fixing three issues rooted in running an X
greeter on a multi-monitor / HiDPI / Wayland box: a blank output, tiny
scaling, and the Xorg->Wayland VT/seat handover race that left the Hyprland
session with dead keyboard/mouse.

- etc-greetd/: greetd config, ReGreet config (cyberqueer GTK4 theme, larger
  font, wallpaper background), cage session launcher, and a password + FIDO
  PAM stack (no securetty/OATH, which a graphical greeter cannot drive).
- greetd-regreet.sh: install/enable module, mutually exclusive with the
  lightdm and ly greeter modules.
- Greeter background tracks the desktop wallpaper: a root systemd .path unit
  mirrors the picker's choice to a greeter-readable path (the user's home is
  0700, so the unprivileged greeter cannot read it directly).

The LightDM path is kept as a dormant alternative and hardened en route:
pam_u2f (FIDO) added to its PAM stack, a HiDPI + multi-monitor
display-setup.sh, a larger greeter font, and the same wallpaper-sync units.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/astal-menu
Amir Alexander Abdelbaki 2026-07-04 03:16:12 +02:00
parent d1e5f765aa
commit 29f6c62008
11 changed files with 395 additions and 2 deletions

17
etc-greetd/config.toml Normal file
View File

@ -0,0 +1,17 @@
# greetd config — Wayland-native cyberqueer greeter (ReGreet inside cage).
#
# Deployed to /etc/greetd/config.toml by
# setup/modules/optional-Modules/apps/greetd-regreet.sh.
#
# Replaces the LightDM/lightdm-gtk-greeter (Xorg) path: a wlroots greeter drives
# every monitor natively (no blank output) and hands input straight to the
# Wayland session — no Xorg→Wayland VT handover race.
[terminal]
# Own tty1 (the getty there is disabled by the greeter module).
vt = 1
[default_session]
# regreet-session.sh sets greeter env, then execs `cage -s -- regreet`.
command = "/etc/greetd/regreet-session.sh"
user = "greeter"

16
etc-greetd/pam.d-greetd Normal file
View File

@ -0,0 +1,16 @@
#%PAM-1.0
# PAM stack for the greetd/ReGreet greeter.
#
# Deployed to /etc/pam.d/greetd by greetd-regreet.sh (@HOST@ → this machine's
# hostname at install time). Mirrors the working lightdm stack: password via
# system-login plus a required FIDO/U2F touch. `nouserok` lets users without an
# enrolled key log in with their password alone.
#
# NOTE: intentionally simpler than a tuigreet multi-factor stack — no
# pam_securetty / pam_oath, which a graphical greeter's single password field
# cannot drive cleanly.
auth include system-login
auth required pam_u2f.so nouserok cue origin=pam://@HOST@ appid=pam://@HOST@
account include system-login
password include system-login
session include system-login

View File

@ -0,0 +1,16 @@
#!/bin/sh
# regreet-session.sh — greetd session command: run ReGreet inside a cage kiosk.
#
# greetd runs this as the unprivileged `greeter` user. cage is a minimal
# wlroots/Wayland compositor; `-s` allows VT switching (so Ctrl+Alt+F-keys still
# reach a console). Env here tunes the greeter's look before ReGreet starts.
#
# Scaling: cage renders at output scale 1. Bump XCURSOR_SIZE for a larger cursor;
# uncomment GDK_SCALE to integer-scale the whole greeter (careful on mixed-DPI
# multi-monitor — it scales every output). Prefer regreet.toml's font_name for
# finer control.
export XCURSOR_SIZE=32
# export GDK_SCALE=2
exec cage -s -- regreet

42
etc-greetd/regreet.toml Normal file
View File

@ -0,0 +1,42 @@
# regreet.toml — cyberqueer-themed ReGreet greeter for greetd (Wayland-native)
#
# Deployed to /etc/greetd/regreet.toml by
# setup/modules/optional-Modules/apps/greetd-regreet.sh.
#
# ReGreet is GTK4, so it reuses the cyberqueer GTK theme's gtk-4.0 assets
# (/usr/share/themes/cyberqueer) — matching the desktop without hardcoded colours
# here. Being a wlroots/Wayland greeter it drives every monitor natively (no
# blank outputs) and hands input straight to the Wayland session with no
# Xorg→Wayland VT handover.
# Remember the last user + session and skip the pickers.
skip_selection = false
[background]
# Tracks the user's current desktop wallpaper: the picker writes
# ~/.config/hypr/usr/wallpaper.conf and the lightdm-greeter-wallpaper.path unit
# copies the chosen image here (a path the unprivileged greeter can read).
path = "/usr/share/backgrounds/lightdm-greeter-bg.png"
fit = "Cover"
[GTK]
application_prefer_dark_theme = true
cursor_theme_name = "Adwaita"
cursor_blink = true
# Bumped well above the 11pt the old greeter used, so the greeter reads large on
# the 4K panel. Tune here (or via GDK_SCALE in regreet-session.sh) to taste.
font_name = "JetBrains Mono 16"
icon_theme_name = "Adwaita"
theme_name = "cyberqueer"
[commands]
reboot = ["systemctl", "reboot"]
poweroff = ["systemctl", "poweroff"]
[appearance]
greeting_msg = "Welcome back"
[widget.clock]
format = "%H:%M"
resolution = "1s"
label_width = 150

View File

@ -0,0 +1,56 @@
#!/bin/sh
# display-setup.sh — LightDM greeter display + HiDPI setup
#
# Runs as root on the greeter's X server BEFORE the greeter starts (wired up via
# `display-setup-script=` in /etc/lightdm/lightdm.conf).
#
# Why this exists: lightdm-gtk-greeter is an Xorg client. On a multi-monitor box
# a bare X server may leave some outputs disabled (a "blank" monitor at the login
# screen) and drops the login form onto whichever output it picks as primary —
# often a small side panel. It also defaults to 96 DPI, so on a 4K panel the
# whole greeter is tiny.
#
# So, name-agnostically (survives GPU/cable/port changes):
# 1. Enable every connected output at its preferred mode, laid out left→right.
# 2. Make the widest active output primary, so the login form lands on the main
# (here: 4K) monitor instead of a small one.
# 3. Raise the DPI so text, icons, form and cursor are legibly large. GTK reads
# Xft.dpi from the X resource database; `xrandr --dpi` covers anything that
# reads the server core DPI instead.
#
# Tuning: bump/lower the 144 DPI (96 = 1.0x, 144 = 1.5x, 192 = 2.0x) to taste.
LOG=/var/log/lightdm/display-setup.log
exec >>"$LOG" 2>&1
echo "=== display-setup $(date) ==="
[ -x /usr/bin/xrandr ] || { echo "xrandr missing — skipping"; exit 0; }
# 1. Enable + position every connected output, left to right in connection order.
prev=""
for out in $(xrandr --query | awk '/ connected/{print $1}'); do
if [ -z "$prev" ]; then
xrandr --output "$out" --auto --pos 0x0
else
xrandr --output "$out" --auto --right-of "$prev"
fi
prev="$out"
done
# 2. Primary = widest currently-active output (parse the WxH+X+Y geometry).
best_out=$(xrandr --query | awk '
/ connected/{o=$1}
match($0, /[0-9]+x[0-9]+\+[0-9]+\+[0-9]+/){
split(substr($0, RSTART, RLENGTH), a, /[x+]/)
if (a[1]+0 > best){ best=a[1]+0; bo=o }
}
END{ print bo }')
[ -n "$best_out" ] && xrandr --output "$best_out" --primary
# 3. HiDPI scaling for the greeter.
xrandr --dpi 144
if [ -n "$DISPLAY" ] && command -v xrdb >/dev/null 2>&1; then
printf 'Xft.dpi: 144\nXcursor.size: 36\n' | xrdb -merge
fi
echo "primary=${best_out:-<none>} dpi=144"

View File

@ -0,0 +1,41 @@
#!/bin/sh
# lightdm-greeter-wallpaper — mirror the user's desktop wallpaper to the greeter.
#
# The wallpaper picker (~/.config/scripts/wallpaper-picker) writes the chosen
# per-monitor wallpapers to ~/.config/hypr/usr/wallpaper.conf. This helper copies
# the wallpaper of the greeter's main monitor to a world-readable system path so
# the LightDM greeter — which runs as the unprivileged 'lightdm' user and cannot
# read a 0700 home — can use it as its background.
#
# Run as root (it needs to read the user's home) via lightdm-greeter-wallpaper.path,
# which fires whenever wallpaper.conf changes. Usage:
# lightdm-greeter-wallpaper <username>
#
# Monitor choice: prefer $PREFERRED_MONITOR (the main/4K panel that hosts the
# login form); fall back to the first wallpaper entry. Set PREFERRED_MONITOR in
# the environment (the .service does) to match your primary output.
set -eu
USER_NAME="${1:?usage: lightdm-greeter-wallpaper <username>}"
PREFERRED="${PREFERRED_MONITOR:-}"
DEST="/usr/share/backgrounds/lightdm-greeter-bg.png"
HOME_DIR="$(getent passwd "$USER_NAME" | cut -d: -f6)"
[ -n "$HOME_DIR" ] || { echo "no home for $USER_NAME" >&2; exit 0; }
STATE="$HOME_DIR/.config/hypr/usr/wallpaper.conf"
[ -r "$STATE" ] || { echo "no wallpaper state at $STATE" >&2; exit 0; }
# Path for the preferred monitor, else the first wallpaper path in the file.
src=""
if [ -n "$PREFERRED" ]; then
src="$(awk -v m="$PREFERRED" '
$1=="monitor" && $3==m { want=1; next }
want && $1=="path" { print $3; exit }' "$STATE")"
fi
[ -n "$src" ] || src="$(awk '$1=="path"{print $3; exit}' "$STATE")"
[ -n "$src" ] && [ -r "$src" ] || { echo "no readable wallpaper resolved" >&2; exit 0; }
install -Dm644 "$src" "$DEST"
echo "greeter background <- $src"

View File

@ -0,0 +1,11 @@
# Watch the wallpaper picker's state file; refresh the greeter background on change.
# @USER@ is filled in at install time by lightdm.sh.
[Unit]
Description=Watch @USER@'s wallpaper selection for the LightDM greeter
[Path]
PathModified=/home/@USER@/.config/hypr/usr/wallpaper.conf
Unit=lightdm-greeter-wallpaper.service
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,10 @@
# Mirror the user's current desktop wallpaper to the LightDM greeter background.
# Triggered by lightdm-greeter-wallpaper.path when wallpaper.conf changes.
# @USER@ / @PREFERRED_MONITOR@ are filled in at install time by lightdm.sh.
[Unit]
Description=Sync LightDM greeter background to @USER@'s desktop wallpaper
[Service]
Type=oneshot
Environment=PREFERRED_MONITOR=@PREFERRED_MONITOR@
ExecStart=/usr/local/bin/lightdm-greeter-wallpaper @USER@

View File

@ -6,11 +6,22 @@
# Colours are inherited from the cyberqueer GTK theme (installed to
# /usr/share/themes/cyberqueer), which is an apply-theme.sh SYS_FILES target —
# so there are no hardcoded palette hex values to keep in sync here.
#
# Scaling: the greeter is enlarged mainly via the X DPI set in display-setup.sh
# (Xft.dpi 144). font-name is kept modest because point sizes already scale with
# that DPI; bump the DPI in display-setup.sh to make everything bigger still.
#
# Background: tracks the user's current desktop wallpaper. The picker writes
# ~/.config/hypr/usr/wallpaper.conf; a root .path unit
# (lightdm-greeter-wallpaper.path) copies the chosen wallpaper to the path below,
# which the greeter (running as the unprivileged 'lightdm' user, unable to read a
# 0700 home) can read. cyberqueer-skull.png is the first-boot fallback, seeded by
# lightdm.sh if no wallpaper has been chosen yet.
[greeter]
theme-name = cyberqueer
icon-theme-name = Papirus-Dark
background = /usr/share/backgrounds/cyberqueer-skull.png
font-name = JetBrains Mono 11
background = /usr/share/backgrounds/lightdm-greeter-bg.png
font-name = JetBrains Mono 12
xft-antialias = true
xft-hintstyle = hintfull
indicators = ~host;~spacer;~clock;~spacer;~session;~power

View File

@ -0,0 +1,101 @@
#!/bin/bash
# greetd-regreet.sh — greetd + ReGreet (cyberqueer-themed, Wayland-native greeter)
#
# The Wayland-native alternative to the lightdm module. lightdm-gtk-greeter is an
# Xorg client, which on this multi-monitor / HiDPI / Wayland box means: a monitor
# can come up blank, the greeter renders tiny, and the Xorg→Wayland VT handover
# can race so the Hyprland session starts with dead input. ReGreet runs on
# wlroots (inside a cage kiosk), so it drives every output natively and hands
# input straight to the Wayland session — none of those failure modes.
#
# It reuses the cyberqueer GTK theme (gtk-4.0 assets) and the greeter-background
# sync (the wallpaper picker's choice, mirrored to a greeter-readable path by the
# lightdm-greeter-wallpaper.path unit — shared, greeter-agnostic despite the name).
#
# Mutually exclusive with the lightdm and ly greeter modules: enabling greetd
# here disables the others and the tty1 getty so exactly one greeter runs.
set -euo pipefail
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
# apps/ → optional-Modules → modules → setup → repo root (four levels up).
DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)"
ETC_GREETD="$DOTFILES_DIR/etc-greetd"
ETC_LIGHTDM="$DOTFILES_DIR/etc-lightdm" # shared wallpaper-sync helper + units
log "Installing greetd + ReGreet + cage..."
sudo pacman -S --noconfirm --needed greetd greetd-regreet cage
# ── cyberqueer GTK theme (ReGreet is GTK4 → needs the gtk-4.0 assets) ──────────
GTK_THEME_SRC="$DOTFILES_DIR/gtk-themes/cyberqueer"
if [[ -d "$GTK_THEME_SRC" ]]; then
log "Installing cyberqueer GTK theme to /usr/share/themes..."
sudo cp -r "$GTK_THEME_SRC" /usr/share/themes/
else
warn "cyberqueer GTK theme not found at $GTK_THEME_SRC — greeter uses the default GTK theme."
fi
# ── Skull fallback background (used until a wallpaper is picked) ───────────────
SKULL_SRC="$DOTFILES_DIR/resources/bg-skull.svg"
SKULL_DST="/usr/share/backgrounds/cyberqueer-skull.png"
if [[ -f "$SKULL_SRC" ]]; then
command -v rsvg-convert &>/dev/null || sudo pacman -S --noconfirm --needed librsvg
TMP_BG="$(mktemp /tmp/greeter-skull.XXXXXX.png)"
trap 'rm -f "$TMP_BG"' EXIT
rsvg-convert -w 1920 "$SKULL_SRC" -o "$TMP_BG"
sudo install -Dm644 "$TMP_BG" "$SKULL_DST"
fi
# ── Deploy greetd + ReGreet configuration ─────────────────────────────────────
log "Deploying greetd config, ReGreet config, and session launcher..."
sudo install -Dm644 "$ETC_GREETD/config.toml" /etc/greetd/config.toml
sudo install -Dm644 "$ETC_GREETD/regreet.toml" /etc/greetd/regreet.toml
sudo install -Dm755 "$ETC_GREETD/regreet-session.sh" /etc/greetd/regreet-session.sh
# ── PAM: password (system-login) + required FIDO/U2F touch ────────────────────
# Replace any pre-existing greetd stack (e.g. a tuigreet securetty+oath one that
# a graphical greeter cannot drive), keeping a timestamped backup.
U2F_HOST="$(hostnamectl --static hostname 2>/dev/null || hostname)"
if [[ -f "$ETC_GREETD/pam.d-greetd" ]]; then
[[ -f /etc/pam.d/greetd ]] && sudo cp -a /etc/pam.d/greetd "/etc/pam.d/greetd.bak-$(date +%s)"
log "Deploying /etc/pam.d/greetd (password + FIDO, origin pam://${U2F_HOST})..."
tmp="$(mktemp)"
sed "s|@HOST@|${U2F_HOST}|g" "$ETC_GREETD/pam.d-greetd" > "$tmp"
sudo install -Dm644 "$tmp" /etc/pam.d/greetd
rm -f "$tmp"
else
warn "PAM template not found — leaving /etc/pam.d/greetd as-is (FIDO may not work)."
fi
# ── Greeter background tracks the user's desktop wallpaper (shared units) ──────
TARGET_USER="${SUDO_USER:-$(id -un)}"
PREF_MON="${LIGHTDM_GREETER_MONITOR:-}"
WP_HELPER_SRC="$ETC_LIGHTDM/lightdm-greeter-wallpaper"
if [[ -f "$WP_HELPER_SRC" ]]; then
log "Installing greeter wallpaper-sync helper + units (user: $TARGET_USER)..."
sudo install -Dm755 "$WP_HELPER_SRC" /usr/local/bin/lightdm-greeter-wallpaper
for unit in lightdm-greeter-wallpaper.service lightdm-greeter-wallpaper.path; do
tmp="$(mktemp)"
sed -e "s|@USER@|${TARGET_USER}|g" -e "s|@PREFERRED_MONITOR@|${PREF_MON}|g" \
"$ETC_LIGHTDM/$unit" > "$tmp"
sudo install -Dm644 "$tmp" "/etc/systemd/system/$unit"
rm -f "$tmp"
done
sudo systemctl daemon-reload
enable_service lightdm-greeter-wallpaper.path
sudo PREFERRED_MONITOR="$PREF_MON" /usr/local/bin/lightdm-greeter-wallpaper "$TARGET_USER" || true
if [[ ! -f /usr/share/backgrounds/lightdm-greeter-bg.png && -f "$SKULL_DST" ]]; then
sudo install -Dm644 "$SKULL_DST" /usr/share/backgrounds/lightdm-greeter-bg.png
fi
else
warn "Wallpaper-sync helper not found — greeter background will not track the wallpaper."
[[ -f "$SKULL_DST" ]] && sudo install -Dm644 "$SKULL_DST" /usr/share/backgrounds/lightdm-greeter-bg.png
fi
# ── Make greetd the sole display manager ──────────────────────────────────────
sudo systemctl disable getty@tty1.service || true
disable_service lightdm.service
disable_service ly@tty1.service
enable_service greetd.service
log "greetd + ReGreet installed and enabled. The Wayland greeter starts on next boot."

View File

@ -60,6 +60,78 @@ else
warn "Greeter config not found at $GREETER_CONF_SRC — LightDM will use greeter defaults."
fi
# ── HiDPI + multi-monitor display setup ───────────────────────────────────────
# lightdm-gtk-greeter is an Xorg client; on a multi-monitor / HiDPI box X must be
# told to enable every output and scale up, or a monitor stays blank at the login
# screen and the greeter is tiny. display-setup.sh (root, before the greeter)
# handles both. Wire it into lightdm.conf's [Seat:*] idempotently.
DISPLAY_SETUP_SRC="$DOTFILES_DIR/etc-lightdm/display-setup.sh"
if [[ -f "$DISPLAY_SETUP_SRC" ]]; then
log "Deploying greeter display-setup script..."
sudo install -Dm755 "$DISPLAY_SETUP_SRC" /etc/lightdm/display-setup.sh
# xrandr/xrdb are needed by the script at greeter time.
sudo pacman -S --noconfirm --needed xorg-xrandr xorg-xrdb
# Match only the real setting form (#display-setup-script=), never the
# "# display-setup-script = <description>" documentation comment (note the
# space after '#') — otherwise the doc line gets turned into a stray active
# setting under [LightDM].
if grep -qE '^\s*display-setup-script\s*=\s*/etc/lightdm/display-setup.sh' /etc/lightdm/lightdm.conf; then
skip "display-setup-script already wired in lightdm.conf"
elif grep -qE '^\s*#?display-setup-script\s*=' /etc/lightdm/lightdm.conf; then
sudo sed -i -E 's|^\s*#?display-setup-script\s*=.*|display-setup-script=/etc/lightdm/display-setup.sh|' /etc/lightdm/lightdm.conf
log "Wired display-setup-script into lightdm.conf"
else
sudo sed -i '/^\[Seat:\*\]/a display-setup-script=/etc/lightdm/display-setup.sh' /etc/lightdm/lightdm.conf
log "Added display-setup-script to [Seat:*] in lightdm.conf"
fi
else
warn "display-setup.sh not found at $DISPLAY_SETUP_SRC — greeter monitors/scaling unmanaged."
fi
# ── FIDO / U2F login at the greeter ───────────────────────────────────────────
# LightDM's PAM stack only `include`s system-login, which has no pam_u2f — so
# hardware-key login (supported by the other greeters here) would not work. Add
# it, matching this machine's origin/appid (pam://$hostname, the pam_u2f default
# used at enrolment). `nouserok` lets users without an enrolled key still log in
# with their password alone.
PAM_LIGHTDM=/etc/pam.d/lightdm
if [[ -f "$PAM_LIGHTDM" ]] && ! grep -q pam_u2f "$PAM_LIGHTDM"; then
U2F_HOST="$(hostnamectl --static hostname 2>/dev/null || hostname)"
log "Enabling pam_u2f (FIDO) for the LightDM greeter..."
sudo sed -i "/^auth\s\+include\s\+system-login/a auth required pam_u2f.so nouserok cue origin=pam://${U2F_HOST} appid=pam://${U2F_HOST}" "$PAM_LIGHTDM"
else
skip "pam_u2f already present in $PAM_LIGHTDM (or file missing)"
fi
# ── Greeter background tracks the user's desktop wallpaper ─────────────────────
# The wallpaper picker writes ~/.config/hypr/usr/wallpaper.conf; a root .path unit
# copies the chosen wallpaper to a system path the greeter can read (the user's
# home is 0700). Install the helper + units with the target user substituted in.
TARGET_USER="${SUDO_USER:-$(id -un)}"
PREF_MON="${LIGHTDM_GREETER_MONITOR:-}"
WP_HELPER_SRC="$DOTFILES_DIR/etc-lightdm/lightdm-greeter-wallpaper"
if [[ -f "$WP_HELPER_SRC" ]]; then
log "Installing greeter wallpaper-sync helper + units (user: $TARGET_USER)..."
sudo install -Dm755 "$WP_HELPER_SRC" /usr/local/bin/lightdm-greeter-wallpaper
for unit in lightdm-greeter-wallpaper.service lightdm-greeter-wallpaper.path; do
tmp="$(mktemp)"
sed -e "s|@USER@|${TARGET_USER}|g" -e "s|@PREFERRED_MONITOR@|${PREF_MON}|g" \
"$DOTFILES_DIR/etc-lightdm/$unit" > "$tmp"
sudo install -Dm644 "$tmp" "/etc/systemd/system/$unit"
rm -f "$tmp"
done
sudo systemctl daemon-reload
enable_service lightdm-greeter-wallpaper.path
# Seed the greeter background now: user's current wallpaper if set, else skull.
sudo PREFERRED_MONITOR="$PREF_MON" /usr/local/bin/lightdm-greeter-wallpaper "$TARGET_USER" || true
if [[ ! -f /usr/share/backgrounds/lightdm-greeter-bg.png && -f "$SKULL_DST" ]]; then
sudo install -Dm644 "$SKULL_DST" /usr/share/backgrounds/lightdm-greeter-bg.png
fi
else
warn "Wallpaper-sync helper not found — greeter background will not track the desktop wallpaper."
[[ -f "$SKULL_DST" ]] && sudo install -Dm644 "$SKULL_DST" /usr/share/backgrounds/lightdm-greeter-bg.png
fi
# ── Make LightDM the sole display manager ─────────────────────────────────────
# LightDM manages its own VT; free tty1 and disable the competing greeters so
# nothing else races it.