Files
fblogin/README.md
2025-10-22 05:04:19 -04:00

9.5 KiB
Raw Permalink Blame History

/¯¯¯¯/\¯¯¯¯\·. ''/¯¯¯¯/\¯¯¯¯\·. |¯¯¯¯|·.' '/¯¯¯¯/\¯¯¯¯\·.''/¯¯¯¯/\¯¯¯¯\·. |¯¯¯¯|·.'/¯¯¯¯/·.
|·.·´·|::'¯¯¯¯·.\|·´·.·´|://·.|·.·´·|:::' |·´·.·´|::|·.·´·|:::|·.·´·|:::¯¯¯¯·.\|.·´·.·|:::|.·´·.·|\¯¯¯¯\·. |·.·´·|\¯¯¯¯\·.¨ '|·´·.·´|:\¯¯¯¯\·./|·.·´·|::|¯¯¯¯|·.|·´·.·´|::|·.·´·|:::|·.·´·|:\¯¯¯¯\·. ¨|.·´·.·|:::|.·´·.·|:|·.·´·|:::' |____|::¯¯¯¯·.''_//·.'\____\/____/·.''_//·.''\____\/____/·. ||:::||:||:::' '·.:::::·.¨ ¨ ¨ ¨ ¨ ' ·.::::::::::::'·./ ·.::::::::::::'·./' '·.:::::::::::::·./' ·.::::::::::::'·./ '·.:::::·.'·.:::::··.::::·.

fblogin — framebuffer login for tty1

Framebuffer greeter that:

  • draws directly to fbdev (no X/Wayland),
  • authenticates via PAM (optional fprintd),
  • binds a logind session on tty1,
  • execs a real login shell on the same VT.

Quick start

# Debian/Ubuntu deps
sudo apt-get install -y build-essential libpam0g-dev libfprint-dev fprintd pkg-config

# build + install (binary, unit, pam, banner, manpage)
make
sudo make install-all

# logging (global file, debug on)
sudo install -d -m 0755 /var/log/fblogin
sudo install -m 0640 /dev/null /var/log/fblogin/fblogin.log
sudo chown root:adm /var/log/fblogin/fblogin.log

# unit env (drop-in)
sudo systemctl edit fblogin@tty1.service
# [Service]
# Environment=FBLOGIN_DEBUG=1
# Environment=FBLOGIN_LOG_FILE=/var/log/fblogin/fblogin.log

# logrotate (SIGUSR1 reopen)
sudo tee /etc/logrotate.d/fblogin >/dev/null <<'ROT'
/var/log/fblogin/fblogin.log {
  rotate 7
  daily
  missingok
  notifempty
  compress
  create 0640 root adm
  postrotate
    systemctl kill -s SIGUSR1 fblogin@tty1.service 2>/dev/null || true
  endscript
}
ROT

sudo systemctl daemon-reload
sudo systemctl enable --now fblogin@tty1.service

Switch to tty1, log in (fingerprint or password). You land in a login shell on tty1; start your session from there (e.g., startx).


Contents


Scope

Is

  • Greeter for tty1 using fbdev.
  • PAM client that opens a proper logind session.
  • Hands control to a login shell; session startup remains user-controlled.

Is not

  • Full display manager (no seat juggling, no VT switching).
  • Compositor or X/Wayland launcher.
  • Aggressive manager of .Xauthority (guidance only).

Features

  • Env-aware logging
    • FBLOGIN_DEBUG=1 toggles debug.
    • FBLOGIN_LOG_FILE=… pins a global log file (no per-user switch).
    • SIGUSR1 reopens the forced log (for logrotate).
  • CLI
    • --help, --version (from include/version.h)
    • --log-file=/path (implies debug; pins logging)
    • --no-fp, --fp-max=N (UI hint; PAM remains authoritative)
    • --dev (relax in-session guard), --debug
  • Greeter keeper
    • Greeter PAM session in a child, held open until auth, then closed.
  • sd-login diagnostics (if linked with libsystemd)
  • Self-pipe signal wakeups (no SA_RESTART surprises)

Repository layout

include/
  config.h  fb.h  input.h  pam_auth.h  ui.h  version.h
man/
  fblogin.1
src/
  fb.c  input.c  pam_auth.c  ui.c  main.c
system/
  systemd/fblogin@.service
  pamd/fblogin
  issue/issue.fblogin
docs/
  ARCHITECTURE.md  CHANGELOG.md  Contributing.md  TODO.md
Makefile
setup.sh
README.md

Build/install targets

Variables:

  • PREFIX=/usr/local (default)
  • BINDIR=$(PREFIX)/bin
  • MANPREFIX=$(PREFIX)/share/man, MAN1DIR=$(MANPREFIX)/man1
  • ISSUE_FILE=system/issue/issue.fblogin

Targets:

make                 # build
make debug|release|asan

sudo make install            # binary (4755)
sudo make install-systemd    # unit
sudo make install-pam        # /etc/pam.d/fblogin
sudo make install-issue      # /etc/issue.fblogin (optional)
sudo make install-man        # manpage
sudo make install-all        # all above

sudo make uninstall
sudo make uninstall-systemd
sudo make uninstall-pam
sudo make uninstall-issue
sudo make uninstall-man
sudo make uninstall-all

Uninstall is symmetric with defaults. If you changed PREFIX/MAN*, pass the same values to uninstall.


Systemd integration

Installed unit (system/systemd/fblogin@.service):

[Unit]
Description=Framebuffer Login on %I
Documentation=man:systemd-logind.service(8) man:logind.conf(5)
After=systemd-user-sessions.service plymouth-quit-wait.service
Conflicts=getty@%i.service
ConditionPathExists=/dev/%I

[Service]
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
StandardInput=tty
StandardOutput=tty
StandardError=tty

PAMName=fblogin
UtmpIdentifier=%I
UtmpMode=user

ExecStart=/usr/local/bin/fblogin
Type=simple
Restart=always
RestartSec=1

[Install]
WantedBy=multi-user.target

Recommended drop-in:

[Service]
Environment=FBLOGIN_DEBUG=1
Environment=FBLOGIN_LOG_FILE=/var/log/fblogin/fblogin.log

Logrotate:

/var/log/fblogin/fblogin.log {
  rotate 7
  daily
  missingok
  notifempty
  compress
  create 0640 root adm
  postrotate
    systemctl kill -s SIGUSR1 fblogin@tty1.service 2>/dev/null || true
  endscript
}

PAM stack

/etc/pam.d/fblogin:

auth     include   common-auth
account  include   common-account
password include   common-password

session  required  pam_env.so
session  required  pam_loginuid.so
session  include   common-session
session  required  pam_systemd.so
  • pam_loginuid.so sets /proc/self/loginuid.
  • pam_systemd.so registers the session with logind.

Authentication flow

  • Username field first; Tab toggles focus.
  • If password is empty and fingerprints are enabled, try the fingerprint path via PAM.
  • On success: welcome screen → stop greeter keeper → exec users login shell.
  • On failure: prompt for password and proceed via PAM.

Disable fingerprints:

  • --no-fp or FBLOGIN_FINGERPRINT=0.

Session model

  • Runs on tty1 (TTYPath + UtmpMode=user).
  • Opens the user PAM session; logind sees CLASS=user TYPE=tty.
  • Drops to users uid/gid, sets HOME/SHELL/USER/LOGNAME (+ fallback PATH), execs the login shell with -l.
  • Start the graphical session from that shell (e.g., startx).

TTY/signals

  • SIGTERM: graceful shutdown via self-pipe to break poll(2).
  • SIGUSR1: reopen forced log file.
  • SIGINT, SIGQUIT, SIGTSTP: ignored.

Keys:

  • Tab toggle focus, Ctrl-U clear focused field, Ctrl-R reset both, Ctrl-D restart greeter UI.

Logging modes

  • Default: pre-user logs to /tmp/fblogin-$pid.log; after username known, switch to $HOME/.fblogin.log (600, chowned).
  • Forced global: set FBLOGIN_LOG_FILE=/var/log/fblogin/fblogin.log or pass --log-file=/path. Remains on that file and never switches. SIGUSR1 triggers reopen. Requires FBLOGIN_DEBUG=1 or --debug (implied by --log-file).

Xauthority/startx notes

Do not export XAUTHORITY globally in ~/.zshenv.

If keeping Xauthority under XDG_RUNTIME_DIR only on tty1, add to ~/.config/zsh/.zlogin:

if [[ -z "$DISPLAY" && -z "$SSH_TTY" && "$(tty)" == "/dev/tty1" ]]; then
  if [[ -n "$XDG_RUNTIME_DIR" && -O "$XDG_RUNTIME_DIR" ]]; then
    AUTH="${XDG_RUNTIME_DIR}/x11/Xauthority"
    install -d -m 700 -- "${AUTH:h}"
    if [[ ! -L "$HOME/.Xauthority" || "$(readlink -f "$HOME/.Xauthority" 2>/dev/null)" != "$AUTH" ]]; then
      rm -f -- "$HOME/.Xauthority"
      ln -s -- "$AUTH" "$HOME/.Xauthority"
    fi
    # exec startx    # optional
  fi
fi

~/.xinitrc should not hard-set XAUTHORITY; let xinit/xauth manage cookies. It can export env to user services if needed.


setup.sh

Automates:

  • PAM file write,
  • unit install,
  • removal of conflicting getty@tty1 override,
  • disabling getty on tty1/tty2,
  • enabling fblogin@tty1,
  • basic verification.

Run with root: sudo ./setup.sh.


Man page

Install via make install-man, then man fblogin.


Security

  • Binary is setuid root (4755).
  • PAM handles credentials; we dont store them.
  • Greeter session isolated; user session opened/closed explicitly.
  • Minimal signal surface; blocking calls interrupted safely.

Troubleshooting

  • stdin is not a TTY: start via fblogin@tty1.service on a VT.
  • PAM session failed (TTY busy / inside a session): use a free VT; dont launch inside an existing session unless --dev.
  • polkit prompts missing: ensure DE/WM started from the tty-backed session (loginctl should show TYPE=tty).
  • fingerprint never matches: enroll with fprintd-enroll, confirm sensor support; disable with --no-fp if needed.
  • logs break after rotation: use the provided logrotate and SIGUSR1 reopen; ensure forced global logging is configured.
  • no /dev/fb0: fbdev not exposed; current code targets fbdev, not DRM/KMS.

Contributing / License / Docs

  • Contribution guide: docs/Contributing.md
  • Architecture notes: docs/ARCHITECTURE.md
  • Changelog: docs/CHANGELOG.md
  • License: LICENSE