9.5 KiB
/¯¯¯¯/\¯¯¯¯\·. ''/¯¯¯¯/\¯¯¯¯\·. |¯¯¯¯|·.' '/¯¯¯¯/\¯¯¯¯\·.''/¯¯¯¯/\¯¯¯¯\·. |¯¯¯¯|·.'/¯¯¯¯/·.
|·.·´·|::'¯¯¯¯·.\|·´·.·´|://·.|·.·´·|:::' |·´·.·´|::|·.·´·|:::|·.·´·|:::¯¯¯¯·.\|.·´·.·|:::|.·´·.·|\¯¯¯¯\·.
|·.·´·|\¯¯¯¯\·.¨ '|·´·.·´|:\¯¯¯¯\·./|·.·´·|::|¯¯¯¯|·.|·´·.·´|::|·.·´·|:::|·.·´·|:\¯¯¯¯\·. ¨|.·´·.·|:::|.·´·.·|:|·.·´·|:::' |____|::¯¯¯¯·.''_//·.'\____\/____/·.''_//·.''\____\/____/·. ||:::||:||:::'
'·.:::::·.¨ ¨ ¨ ¨ ¨ ' ·.::::::::::::'·./ ·.::::::::::::'·./' '·.:::::::::::::·./' ·.::::::::::::'·./ '·.:::::·.'·.:::::··.::::·.
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
- Features
- Repository layout
- Build/install targets
- Systemd integration
- PAM stack
- Authentication flow
- Session model
- TTY/signals
- Logging modes
- Xauthority/startx notes
- setup.sh
- Man page
- Security
- Troubleshooting
- Contributing / License / Docs
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=1toggles debug.FBLOGIN_LOG_FILE=…pins a global log file (no per-user switch).SIGUSR1reopens the forced log (for logrotate).
- CLI
--help,--version(frominclude/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)/binMANPREFIX=$(PREFIX)/share/man,MAN1DIR=$(MANPREFIX)/man1ISSUE_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.sosets/proc/self/loginuid.pam_systemd.soregisters the session with logind.
Authentication flow
- Username field first;
Tabtoggles focus. - If password is empty and fingerprints are enabled, try the fingerprint path via PAM.
- On success: welcome screen → stop greeter keeper → exec user’s login shell.
- On failure: prompt for password and proceed via PAM.
Disable fingerprints:
--no-fporFBLOGIN_FINGERPRINT=0.
Session model
- Runs on tty1 (
TTYPath+UtmpMode=user). - Opens the user PAM session; logind sees
CLASS=user TYPE=tty. - Drops to user’s uid/gid, sets
HOME/SHELL/USER/LOGNAME(+ fallbackPATH), 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 breakpoll(2).SIGUSR1: reopen forced log file.SIGINT,SIGQUIT,SIGTSTP: ignored.
Keys:
Tabtoggle focus,Ctrl-Uclear focused field,Ctrl-Rreset both,Ctrl-Drestart 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.logor pass--log-file=/path. Remains on that file and never switches.SIGUSR1triggers reopen. RequiresFBLOGIN_DEBUG=1or--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@tty1override, - 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 don’t 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.serviceon a VT. - PAM session failed (TTY busy / inside a session): use a free VT; don’t launch inside an existing session unless
--dev. - polkit prompts missing: ensure DE/WM started from the tty-backed session (
loginctlshould showTYPE=tty). - fingerprint never matches: enroll with
fprintd-enroll, confirm sensor support; disable with--no-fpif needed. - logs break after rotation: use the provided logrotate and
SIGUSR1reopen; 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