PLM 免按键人脸登录
Plasma Login Manager 没有让 greeter 自动开始认证的配置。用 Autologin 加一道人脸闸门,开机后通过人脸即可零按键进入桌面,失败则回到正常登录界面。
背景 《人脸登录后的 KWallet 自动解锁》把本文当作背景引用,见文末。
Plasma Login Manager(PLM)的登录界面里,人脸认证要先按 Enter,或者点击登录按钮。Windows Hello 式的“看向摄像头就开始识别”没有对应的配置开关。所以我多少有点标题党了,为了实现这个效果,我没有在 PLM 上死磕,而是转而使用了其他方法。
先决条件
```textCachyOS(Arch 系)|KDE Plasma 6.7.5|Plasma Login Manager 6.7.4 Wayland|biopass-bin 1.4.1|pam 1.7.2
```
本机 biopass 只开人脸(`methods.face.enable: true`,指纹关闭),单次判定约 2 秒。下面所有配置里的 `<用户名>` 是占位符,安装前换成你自己的登录名。
整套改动落在三个地方,没有新增守护进程,也不需要编译任何东西:
| # | 位置 | 作用 |
|---|---|---|
| 1 | `/usr/local/libexec/biopass-pam-auth` |
人脸闸门包装脚本 |
| 2 | `/etc/pam.d/plasmalogin-autologin` |
覆盖上游的放行式 PAM 栈 |
| 3 | `/etc/plasmalogin.conf` |
`[Autologin]` 段指定登录用户 |
现象
在 greeter 前坐定、把脸对着摄像头,界面不会有任何反应,必须先按一次 Enter 或点一次登录按钮,识别才开始。识别本身没问题:一次通过、约 2 秒进桌面。缺的是谁来发起这次认证。
greeter 为什么不自动认证
上游 `src/frontend/greeter/qml/Login.qml`(6.7)里,能发起认证的入口只有三个:
```qmlpasswordBox.onAccepted: startLogin() // 密码框回车 loginButton.onClicked: startLogin() // 点登录按钮 loginButton.Keys.onReturnPressed: clicked() // 按钮聚焦时回车
```
而负责初始化界面的 `Component.onCompleted` 只做三件事:填充上次登录的用户名、把焦点放到密码框、同步界面状态。它没有调用 `startLogin()`。
想加一个触发点也走不通:
- 没有可替换的主题。 `
src/frontend/greeter/CMakeLists.txt` 用 `ecm_add_qml_module(plasma-login-greeter URI "org.kde.plasma.login")` 加 `ecm_target_qml_sources(...)` 把 `Main.qml`、`Login.qml` 这些文件编进了二进制资源;`src/frontend/settings/models/` 下只有 `sessionmodel`、`usermodel`。SDDM 那种“放一个主题目录就换掉整个界面”的做法在这里不存在。 - 没有对应配置键。 上游 `
src/common/mainconfig.kcfg`(6.7)里的键是全部:`X11.*`、`Wayland.SessionLogFile`、`Autologin.User/Session/Relogin`、`General.Namespaces`、`Users.DefaultPath`。`AutoAuth`、`Biometric`、`AutoStartAuth` 这一类键一个都没有。
所以这是当前设计,不是配置写错了。改 greeter 配置或主题都不会凭空多出一个认证触发点。
为什么不能直接把 biopass 标成 required
在动手前得先绕开一个坑,它决定后面为什么用一个包装脚本而不是直接引用 biopass 的 PAM 模块。
biopass 的 PAM 模块(上游 `auth/pam/pam.cc`)把助手的退出码映射成三种 PAM 返回值:
| helper 退出码 | PAM 返回值 | 什么时候出现 |
|---|---|---|
| 0 | `PAM_SUCCESS` |
人脸匹配 |
| 2 | `PAM_IGNORE` |
跑不起来:摄像头打不开、模型缺失、该用户无人脸 |
| 其他非 0 | `PAM_AUTH_ERR` |
人脸不匹配 |
问题出在 `PAM_IGNORE` 上。Linux-PAM 的 `pam_dispatch.c` 里,`required` 展开成 `[success=ok new_authtok_reqd=ok ignore=ignore default=bad]`,也就是说模块返回 `PAM_IGNORE` 时对应的动作是 ignore,直接跳过,完全不影响整个栈的成败。于是下面这种写法看起来是把人脸设成了必须项,实际不是:
```iniauth required libbiopass_pam.so # 摄像头坏了 → PAM_IGNORE → 不算失败 auth required pam_permit.so # → 放行 → 无认证进入桌面
```
对 `sudo` 那种“先试人脸、失败回退密码”的场景,这个特性无害;对 autologin 这种无人值守的闸门就是漏洞,摄像头一坏,闸门自己开了。
规避办法是用 `pam_exec` 调 biopass 的助手。`pam_exec`(`man pam_exec`)只看进程退出码,0 是通过、非 0 是失败,它没有 `PAM_IGNORE` 这种中间态:
```plaintexthelper 退出码 0 → pam_exec 返回 PAM_SUCCESS helper 退出码非 0 → pam_exec 返回 PAM_SERVICE_ERR / PAM_AUTH_ERR
```
于是映射关系变成干净的二分:能跑通并识别成功才算通过,其余一切情况——没识别到、摄像头不可用、助手缺失、脚本自身出错——都是失败。autologin 失败会回落到登录界面,用户还能输密码,不会把自己锁在外面。
代价是多一个包装脚本。后面会看到,它同时还要负责给钱包传递密钥。
选择 Autologin
PLM 的 Autologin 恰好把认证放在了 greeter 之前。上游 `src/daemon/Display.cpp` 的 `Display::start()`:
```cppif (m_autologinSession.isValid()) { m_auth->setAutologin(true); if (startAuth(m_autologinUser, QString(), m_autologinSession)) return true; // ← 前两个参数:用户、空密码 else return handleAutologinFailure(); // ← "Autologin failed!" → 拉起 greeter }
```
三个性质正好都用得上:
- 不需要按键——认证发生在 greeter 和合成器启动之前,屏幕上还没有任何可点的东西。
- 失败不锁死——`
handleAutologinFailure()` 会正常拉起登录界面,密码框和人脸重试都在。 - 失败是可见的——日志里留下 `
Autologin failed!` 一行,排查时不用猜。
`PamBackend::start()` 里写明 autologin 用的 PAM 服务名就是 `plasmalogin-autologin`,所以闸门脚本要挂在 `/etc/pam.d/plasmalogin-autologin` 上,而不是 greeter 用的 `/etc/pam.d/plasmalogin`。
运行时序:
```text开机 → plasmalogin 启动 └─ Autologin:跑 plasmalogin-autologin 的 PAM 栈 ├─ 通过 → 创建 Plasma 会话,直接进桌面 └─ 失败 → Autologin failed! → 拉起 greeter
```
这里会出现两个代价,或许你需要自行抉择:
- 认证期间没有界面。 人脸那约 2 秒是黑屏,即便启用了开机动画也看不到。这是“在 greeter 之前认证”的必然结果。
- 开机时不能选会话。 会话由配置里的 `
Session=` 固定。本机本来就固定用 Plasma,实际影响很小(但是诸位使用 Linux 的用户大概率是多会话用户,所以可能你需要做出妥协)。
人脸闸门脚本
脚本要做的事:从 PAM 拿当前用户名和服务名,调 biopass 助手,只把退出码原样交给 `pam_exec`。任何一步不成立都退出非 0。
`pam_exec` 会往子进程环境里放 `PAM_USER`、`PAM_SERVICE`、`PAM_TYPE`。脚本用前两个,并且在任何一个为空时直接拒绝放行。另外 `pam_exec` 默认以调用者的身份执行命令,而 autologin 时代的调用者是 root,所以这个脚本必须是 `root:root 0755`,不能让它可被普通用户改写。
```bash#!/bin/bash # # biopass-pam-auth — 由 pam_exec.so 调用的"人脸闸门" # # 安装位置:/usr/local/libexec/biopass-pam-auth (root:root 0755) # 调用方式:PAM 文件中写 # auth required pam_exec.so quiet /usr/local/libexec/biopass-pam-auth # # 设计原因(勿改成 auth required libbiopass_pam.so): # biopass 的 PAM 模块在"无法运行"(摄像头打不开/模型缺失/该用户无人脸)时 # helper 退出码为 2,模块会把它映射成 PAM_IGNORE;而 PAM 的 required 会忽略 # PAM_IGNORE,配合 autologin 栈里原有的 pam_permit.so 就会变成"无认证放行"。 # pam_exec 只看退出码:0 = 通过,非 0 = 失败(helper 缺失/脚本异常同样失败,fail-closed)。 # # 环境变量由 pam_exec 注入:PAM_USER / PAM_SERVICE / PAM_TYPE 等。 # # SPDX-License-Identifier: MIT set -u HELPER="/usr/bin/biopass-helper" LOG_TAG="biopass-pam-auth" user="${PAM_USER:-}" service="${PAM_SERVICE:-plasmalogin-autologin}" if [[ -z "${user}" ]]; then logger -t "${LOG_TAG}" -p auth.warning "PAM_USER 为空,拒绝放行(fail-closed)" exit 1 fi if [[ ! -x "${HELPER}" ]]; then logger -t "${LOG_TAG}" -p auth.err "找不到可执行的 ${HELPER},拒绝放行(fail-closed)" exit 1 fi # 让 biopass 助手能定位该用户的数据目录(人脸库/配置);失败也不影响,仅尽力而为 home="$(getent passwd "${user}" | cut -d: -f6)" if [[ -n "${home}" && -d "${home}" ]]; then export HOME="${home}" fi export USER="${user}" export LOGNAME="${user}" start="${SECONDS}" "${HELPER}" auth --username "${user}" --service "${service}" rc=$? elapsed=$(( SECONDS - start )) if [[ "${rc}" -eq 0 ]]; then logger -t "${LOG_TAG}" -p auth.info "人脸认证通过 user=${user} service=${service} 用时 ${elapsed}s" exit 0 fi # rc=2 表示"没有可用认证方式"(例如摄像头不可用)——必须按失败处理,绝不能放行 logger -t "${LOG_TAG}" -p auth.warning "人脸认证未通过 user=${user} service=${service} rc=${rc} 用时 ${elapsed}s" exit "${rc}"
```
几个处理值得单独说明:
- `
PAM_USER` 为空就 exit 1。 拿不到用户名等于无法认证,此时放行是最坏的选择。 - 找不到助手就 exit 1。 助手路径写错、包被卸载,都应该表现为“认证失败并回落登录界面”,而不是静默通过。
- `
export HOME` 是尽力而为。 biopass 要靠家目录定位人脸库和配置;失败也不影响,所以没有把它当作硬条件。 - 退出码原样透传(`
exit "$rc"`)。 `pam_exec` 只关心 0 与非 0,但日志里留下 1 和 2 的区别,排查时能立刻分清“没认出来”和“摄像头不可用”。 - 耗时写进日志。 那 2 秒黑屏的实际长度从这里能看到,后面调优不用靠感觉。
PAM 顺序
目标文件是 `/etc/pam.d/plasmalogin-autologin`。它在 `/etc/pam.d` 下,优先级高于包内默认的 `/usr/lib/pam.d/plasmalogin-autologin`,所以不会和包管理器抢文件,但升级后要留意上游默认值有没有变。
上游那份是这样的(包内默认,关键在 `pam_permit.so` 那一行):
```iniauth required pam_env.so auth required pam_faillock.so preauth auth required pam_shells.so auth required pam_nologin.so auth required pam_permit.so -auth optional pam_gnome_keyring.so -auth optional pam_kwallet5.so account include system-local-login password include system-local-login session include system-local-login -session optional pam_gnome_keyring.so auto_start -session optional pam_kwallet5.so auto_start
```
改完后:
```ini#%PAM-1.0 # ---- autologin 人脸闸门 + KWallet 自动解锁(本项目新增 2026-09-25)---- # # 1) 人脸闸门用 pam_exec,只按退出码判定:0=通过,非 0=失败。 # 切勿改回 "auth required libbiopass_pam.so":biopass 模块在摄像头不可用/无人脸/ # 模型缺失时返回 PAM_IGNORE(helper exit 2),而 required 会忽略 PAM_IGNORE, # 与 pam_permit.so 组合就会变成"无认证直接进入桌面"。 # # 2) 上游默认的 "auth required pam_permit.so" 已被有意删除: # 没有它以后,认证结果只能来自真实模块;即使将来有人把 pam_exec 改成 optional # 或误删该行,autologin 也只会失败并回落到登录界面(fail-closed),不会静默放行。 # 如需临时关闭人脸闸门:删掉 pam_exec 行并恢复 "auth required pam_permit.so"。 # # 3) pam_walletkey 在认证阶段把 KWallet 密码(/etc/pam_walletkey.key)注入 PAM_AUTHTOK, # 供 pam_kwallet5 在 session 阶段解锁 kdewallet(人脸登录没有密码可抓)。 auth required pam_env.so auth required pam_faillock.so preauth auth required pam_shells.so auth required pam_nologin.so auth required pam_exec.so quiet /usr/local/libexec/biopass-pam-auth -auth optional pam_walletkey.so -auth optional pam_gnome_keyring.so -auth optional pam_kwallet5.so account include system-local-login password include system-local-login session include system-local-login -session optional pam_gnome_keyring.so auto_start -session optional pam_kwallet5.so auto_start
```
两处改动:
- 加 `
auth required pam_exec.so quiet …`。 它排在 `pam_nologin.so` 之后、`pam_permit.so` 原来的位置。`quiet` 让 `pam_exec` 不要把命令输出写进系统日志(`man pam_exec` 里 `stdout` 默认本来就丢进 `/dev/null`,这个标记省掉的是另一路噪音)。 - 删掉 `
auth required pam_permit.so`。 这一行是上游给“谁都不用认证”的 autologin 用的。留着它,认证栈里就有一个永远成功的模块;一旦有人把 `pam_exec` 那行改成 `optional`、或者干脆删掉闸门行,结果不会是“进不去”,而是静默放行。删掉之后,认证结果只能来自真实模块,配置被改坏也只会 autologin 失败并回落登录界面。
顺带说明为什么 `pam_walletkey` 和 `pam_kwallet5` 这两行也在这一段的开头:它们是钱包自动解锁用的,属于人脸登录后的 KWallet 自动解锁那一篇的内容。只想先要免按键登录,可以先只加 `pam_exec` 那一行,把这两行留到之后再加。
Autologin 配置
`/etc/plasmalogin.conf` 只需要 `[Autologin]` 段里的三项:
```ini[Autologin] User=<用户名> Session=plasma Relogin=false
```
- `
User` 指定自动登录哪个账号。不写这一行,autologin 分支根本不会触发。 - `
Session=plasma` 会被 `Session::create()` 自动补成 `.desktop` 并在 `wayland-sessions` 下定位,所以写 `plasma` 和写 `plasma.desktop` 等价。 - `
Relogin` 决定注销之后的行为:`false`(默认值)停在登录界面,需要时再按 Enter;`true` 则注销后立刻再跑一次人脸。想保留“注销出来换个用户”的能力就保持 `false`。
安装
装之前先另开一个 root 终端放着别关。(我们接下来修改的是登录链路上的 PAM 文件,如果写坏了,还有一个已经通过认证的 root shell 可以用来改回来)
脚本会自动备份到 `/var/backups/plasmalogin-face-<时间戳>/`,并且是幂等的,重复执行不会重复插入:
```bash#!/usr/bin/env bash # # Plasma Login Manager 免按键人脸登录 —— Autologin + pam_exec 人脸闸门 安装脚本 # # 用法: sudo bash install.sh # # 安装内容: # 1. /usr/local/libexec/biopass-pam-auth 人脸闸门包装脚本(调 biopass-helper) # 2. /etc/pam.d/plasmalogin-autologin 覆盖 /usr/lib/pam.d 的上游默认(放行式) # 3. /etc/plasmalogin.conf [Autologin] 段加 User=<你>(Relogin 保持默认 false) # # 不重启任何服务(重启 plasmalogin 会杀掉当前会话)。改动在下次开机/重启后生效。 # 幂等:可重复运行;每次都会把原文件备份到 /var/backups/plasmalogin-face-<时间戳>/ # set -euo pipefail STAMP="$(date +%Y%m%d-%H%M%S)" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" BACKUP_DIR="/var/backups/plasmalogin-face-${STAMP}" PLM_CONF="/etc/plasmalogin.conf" AUTOLOGIN_PAM="/etc/pam.d/plasmalogin-autologin" WRAPPER_DST="/usr/local/libexec/biopass-pam-auth" TARGET_USER="${TARGET_USER:-${SUDO_USER:-}}" if [[ "${EUID}" -ne 0 ]]; then echo "错误:请用 root 运行 → sudo bash \"${BASH_SOURCE[0]}\"" >&2 exit 1 fi # 不写死用户名:优先用 TARGET_USER,其次用 sudo 记录的调用者。 if [[ -z "${TARGET_USER}" ]]; then echo "错误:无法确定目标用户(SUDO_USER 为空)" >&2 echo " 请显式指定 → TARGET_USER=你的用户名 sudo -E bash \"${BASH_SOURCE[0]}\"" >&2 exit 1 fi if ! id "${TARGET_USER}" >/dev/null 2>&1; then echo "错误:用户 ${TARGET_USER} 不存在(可用 TARGET_USER=xxx sudo -E bash install.sh 覆盖)" >&2 exit 1 fi for f in biopass-pam-auth plasmalogin-autologin.pam.new; do if [[ ! -e "${SCRIPT_DIR}/${f}" ]]; then echo "错误:缺少交付文件 ${SCRIPT_DIR}/${f}" >&2 exit 1 fi done echo "==> 备份到 ${BACKUP_DIR}" mkdir -p "${BACKUP_DIR}" chmod 700 "${BACKUP_DIR}" for f in "${PLM_CONF}" "${AUTOLOGIN_PAM}"; do [[ -e "${f}" ]] && cp -a "${f}" "${BACKUP_DIR}/$(basename "${f}")" done cp -a /usr/lib/pam.d/plasmalogin-autologin "${BACKUP_DIR}/plasmalogin-autologin.usr-lib.pam.d" 2>/dev/null || true echo " 回滚: sudo bash \"${SCRIPT_DIR}/rollback.sh\" ${BACKUP_DIR}" echo "==> [1/3] 安装人脸闸门脚本" install -D -m 0755 -o root -g root "${SCRIPT_DIR}/biopass-pam-auth" "${WRAPPER_DST}" echo " ${WRAPPER_DST}" echo "==> [2/3] 安装 plasmalogin-autologin 的 PAM 配置" install -m 0644 -o root -g root "${SCRIPT_DIR}/plasmalogin-autologin.pam.new" "${AUTOLOGIN_PAM}" echo " ${AUTOLOGIN_PAM}(覆盖 /usr/lib/pam.d/plasmalogin-autologin,后者保持原样)" echo "==> [3/3] 在 ${PLM_CONF} 的 [Autologin] 段写入 User=${TARGET_USER}(Relogin 不动,保持 false)" before_hash="$(sha256sum "${PLM_CONF}" | awk '{print $1}')" tmp="$(mktemp)" if ! grep -q '^\[Autologin\]' "${PLM_CONF}"; then # 情况 0:[Autologin] 段不存在 → 追加一段 cp -a "${PLM_CONF}" "${tmp}" printf '\n[Autologin]\nUser=%s\n' "${TARGET_USER}" >> "${tmp}" echo " 已追加 [Autologin] 段" elif awk ' /^\[Autologin\]/ { insec = 1; next } /^\[/ { insec = 0 } insec && /^[[:space:]]*User[[:space:]]*=/ { found = 1 } END { exit(found ? 0 : 1) } ' "${PLM_CONF}"; then # 情况 1:段内已有 User= → 原位替换(保持行位置,自动纠正旧值) awk -v user="${TARGET_USER}" ' /^\[Autologin\]/ { insec = 1; print; next } /^\[/ { insec = 0 } insec && /^[[:space:]]*User[[:space:]]*=/ { if (!done) { printf "User=%s\n", user; done = 1 } next } { print } ' "${PLM_CONF}" > "${tmp}" echo " 已原位更新 User=" else # 情况 2:段存在但没有 User= → 紧跟段头插入 awk -v user="${TARGET_USER}" ' { print } /^\[Autologin\]/ && !done { printf "User=%s\n", user; done = 1 } ' "${PLM_CONF}" > "${tmp}" echo " 已在 [Autologin] 段头后插入 User=" fi install -m 0644 -o root -g root "${tmp}" "${PLM_CONF}" rm -f "${tmp}" after_hash="$(sha256sum "${PLM_CONF}" | awk '{print $1}')" if [[ "${before_hash}" == "${after_hash}" ]]; then echo " (内容已是 User=${TARGET_USER},无变化)" fi echo echo "==> PAM 栈自检(应看到 pam_exec 行且顺序正确):" grep -nE 'pam_exec|pam_walletkey|pam_permit' "${AUTOLOGIN_PAM}" | sed 's/^/ /' echo "==> Autologin 配置:" sed -n '/^\[Autologin\]/,/^\[/p' "${PLM_CONF}" | head -n -1 | sed 's/^/ /' cat <<EOF ==> 安装完成。验证方式(不需要注销,先验闸门脚本本身): # 1) 直接以 root 触发一次人脸闸门(应打开摄像头并识别人脸) sudo env PAM_USER=${TARGET_USER} PAM_SERVICE=plasmalogin-autologin ${WRAPPER_DST}; echo "退出码=\$?" # 预期:人脸匹配时退出码 0;遮住脸时非 0;摄像头不可用时为 2(同样会被判为失败) # 注:fish 里没有 $?,请改用 "$status" journalctl -t biopass-pam-auth -b --no-pager | tail -n 5 # 2) 确认当前会话/登录界面未被影响(本脚本不重启任何服务) # 真正的 autologin 效果需要重启系统: # 重启后人脸正常 → 无需任何按键直接进入桌面 # 遮住脸/移开摄像头 → 出现登录界面(按 Enter 可重试人脸,或直接输密码) # 3) 重启后检查日志 journalctl -b -u plasmalogin | grep -iE 'autologin|pam_exec' journalctl -b -u plasmalogin | grep -i pam_kwallet5 # 钱包应自动解锁 ==> 回滚: sudo bash "${SCRIPT_DIR}/rollback.sh" ${BACKUP_DIR} EOF
```
执行:
```bashsudo bash plasmalogin-install.sh
```
脚本不会重启 plasmalogin,因为那会杀掉当前会话。改动在下次开机或重启后生效。
验证
分两层验。先验闸门脚本本身,无需注销或重启:
```bashsudo env PAM_USER=<用户名> \ PAM_SERVICE=plasmalogin-autologin \ /usr/local/libexec/biopass-pam-auth printf 'exit=%s\n' "$?" journalctl -t biopass-pam-auth -b --no-pager | tail -n 5
```
`env PAM_USER=… PAM_SERVICE=…` 就是在手工模拟 `pam_exec` 提供的环境。预期是正脸返回 0、遮住脸返回非 0。
提示
`
$?` 在 fish 里不存在,要用 `$status`。用 bash 跑这段最省事。
本机实测:
- 12:56:36,遮脸 → `
人脸认证未通过 … rc=1 用时 2s`,退出码 1; - 12:57:08,正脸 → `
人脸认证通过 … 用时 2s`,退出码 0。
再验真实开机路径。重启后分两次:一次正常对着镜头,一次把摄像头挡上或移开。
```bashjournalctl -b -u plasmalogin | grep -iE 'autologin|Greeter' journalctl -t biopass-pam-auth -b --no-pager
```
本机两次开机的结果:
- 人脸通过 —— ✅ 全程零按键进桌面,`
Autologin failed` 0 条。 `13:00:10 人脸认证通过 … 用时 2s` → `Session started true` - 人脸失败 —— ✅ 回落登录界面,没有放行。
`
13:03:35 rc=1 用时 4s` → `13:03:37 Autologin failed!` → `Greeter starting...` - greeter 重试 —— ✅ 登录界面里按 Enter 仍能刷脸。
`
13:03:46 Message received from greeter: Login` → `13:03:48 Authentication … successful`
还有两条顺手确认过:TTY(`Ctrl+Alt+F3`)下的 `sudo` 仍能走人脸;`sudo` 走的是 `libbiopass_pam.so` 直连,不经过这个闸门,所以 `journalctl -t biopass-pam-auth` 里不会出现 `service=sudo`——这是预期行为,不是故障。
排错
- 开机后直接出现登录界面,日志里有 `
Autologin failed!`- 可能原因:闸门退出码非 0(没认出脸、摄像头被占用、助手缺失),或 `
/etc/plasmalogin.conf` 里没写 `User=`。 - 怎么办:`
journalctl -t biopass-pam-auth -b` 看退出码和耗时;`sudo env PAM_USER=… PAM_SERVICE=plasmalogin-autologin /usr/local/libexec/biopass-pam-auth; echo $?` 手工复现一次,能立刻区分是脚本问题还是 PAM 顺序问题。
- 可能原因:闸门退出码非 0(没认出脸、摄像头被占用、助手缺失),或 `
- 开机直接进桌面,完全没刷脸
- 可能原因:`
pam_exec` 那行被改成了 `optional`,或者被删掉了——此时剩下的模块里如果还有 `pam_permit.so`,就是无认证放行。 - 怎么办:`
grep -nE 'pam_exec|pam_permit' /etc/pam.d/plasmalogin-autologin`。正确状态是有 `pam_exec`、没有 `pam_permit`,两项都要满足。
- 可能原因:`
- `
journalctl -t biopass-pam-auth` 一条都不出- 可能原因:闸门脚本没被执行(PAM 文件没生效),或脚本内的日志标签被改过。
- 怎么办:确认 `
/etc/pam.d/plasmalogin-autologin` 存在且是 `0644 root:root`;`logger -t biopass-pam-auth test` 确认 `journalctl -t` 过滤本身没问题。
- 认证一直失败,但手工跑脚本是好的
- 可能原因:`
/usr/local/libexec/biopass-pam-auth` 权限不对(`pam_exec` 以 root 身份执行,脚本必须 `root:root 0755`),或脚本首行 shebang 指向的解释器不存在。 - 怎么办:`
ls -l /usr/local/libexec/biopass-pam-auth`,然后 `sudo bash -x /usr/local/libexec/biopass-pam-auth` 跟一遍。
- 可能原因:`
- 重启后确实免按键了,但钱包还是要手动解锁
- 可能原因:`
pam_walletkey` 那两行没加,或顺序排在了 `libbiopass_pam.so` 之后。 - 怎么办:见人脸登录后的 KWallet 自动解锁。
- 可能原因:`
通用性
- `
pam_exec` 只看退出码、不产生 `PAM_IGNORE` —— ✅ 所有用 Linux-PAM 的系统。 - `
Autologin` 在 greeter 之前认证 —— ✅ PLM 当前架构。 - `
pam_exec` 以 root 身份执行命令 —— ✅ Linux-PAM 的行为。 - 上游 `
Login.qml` 没有自动认证入口 —— ✅ PLM 6.7 系列;新版本可能变化,动手前重新核对。 - 具体路径 `
/etc/pam.d/…`、`/usr/local/libexec/…` —— ⚠️ 发行版差异;先确认你的 PAM 与 PLM 装在哪。 - 黑屏时长约 2 秒 —— ⚠️ 取决于摄像头与识别模型,本机实测 2–4 秒。
- `
biopass-helper` 的退出码语义 —— ⚠️ 换用别的生物识别模块要重新核对其退出码约定。 - `
ksecretd` 作为钱包守护进程 —— ⚠️ 由 kwallet-pam 版本决定(6.7.5 的编译默认值)。
回滚
回滚不需要手改文件,脚本会从备份还原,并且在 `/etc/pam.d/plasmalogin-autologin` 原本不存在时把它删掉、回落到 `/usr/lib/pam.d/` 的上游默认:
```bash#!/usr/bin/env bash # # 回滚脚本:撤销 install.sh 的 autologin 人脸闸门改动 # # 用法: sudo bash rollback.sh [备份目录] # 不给参数时自动使用 /var/backups/plasmalogin-face-* 中最新的一份 # set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" PLM_CONF="/etc/plasmalogin.conf" AUTOLOGIN_PAM="/etc/pam.d/plasmalogin-autologin" WRAPPER_DST="/usr/local/libexec/biopass-pam-auth" if [[ "${EUID}" -ne 0 ]]; then echo "错误:请用 root 运行 → sudo bash \"${BASH_SOURCE[0]}\"" >&2 exit 1 fi if [[ $# -ge 1 ]]; then BACKUP_DIR="$1" else BACKUP_DIR="$(ls -1dt /var/backups/plasmalogin-face-* 2>/dev/null | head -n 1 || true)" fi if [[ -z "${BACKUP_DIR:-}" || ! -d "${BACKUP_DIR}" ]]; then echo "错误:找不到备份目录,请手动指定:sudo bash rollback.sh /var/backups/plasmalogin-face-YYYYmmdd-HHMMSS" >&2 exit 1 fi echo "==> 使用备份:${BACKUP_DIR}" if [[ -e "${BACKUP_DIR}/plasmalogin.conf" ]]; then install -m 0644 -o root -g root "${BACKUP_DIR}/plasmalogin.conf" "${PLM_CONF}" echo " ${PLM_CONF} 已还原(autologin User= 被移除)" fi if [[ -e "${BACKUP_DIR}/plasmalogin-autologin" ]]; then install -m 0644 -o root -g root "${BACKUP_DIR}/plasmalogin-autologin" "${AUTOLOGIN_PAM}" echo " ${AUTOLOGIN_PAM} 已还原" else # 安装前 /etc/pam.d 下没有这个文件(包内默认在 /usr/lib/pam.d) rm -f "${AUTOLOGIN_PAM}" echo " ${AUTOLOGIN_PAM}(本次新建)已删除,回落到 /usr/lib/pam.d/plasmalogin-autologin" fi rm -f "${WRAPPER_DST}" rmdir /usr/local/libexec 2>/dev/null || true echo " ${WRAPPER_DST} 已删除" cat <<'EOF' ==> 回滚完成。说明: * 本脚本不重启 plasmalogin(那会杀掉当前会话);下次开机即恢复"显示登录界面"的行为。 * KWallet 相关改动属于另一个档案(biopass-sudo-kwallet),未在此回滚。 EOF
```
```bashsudo bash plasmalogin-rollback.sh # 不给参数 = 用最新那份备份
```
同样不会重启 plasmalogin,下次开机即恢复“显示登录界面”的行为。回滚只覆盖这一篇的改动;钱包那部分属于另一篇,要单独回滚。
什么时候可以退休
犹分省署直,何日是归休。 ——姚合《和膳部李郎中秋夕》
PLM 的这个缺口已经有上游跟踪项:KDE bug 506025(product `plasma-login-manager`,状态 CONFIRMED,wishlist)。那条报告针对的是指纹登录,诉求和这里一样:让登录服务能在 greeter 之外、由界面主动在后台启动一次生物识别,并允许它与密码认证并行。上游目前没有这个能力。
如果后续版本把它做了进去,判断标准是:登录服务能在 greeter 之外安全地启动生物识别,并在失败时回到正常登录流程。满足之后就没有理由继续用 Autologin 绕,届时:
- 删掉 `
/etc/plasmalogin.conf` 里的 `[Autologin] User=`; - 删掉 `
/etc/pam.d/plasmalogin-autologin`,回落到 `/usr/lib/pam.d/` 的上游默认; - 删掉 `
/usr/local/libexec/biopass-pam-auth`。
这三步正好就是回滚脚本做的事,直接跑它也可以。
参考
- Plasma Login Manager 源码(`
src/frontend/greeter/qml/Login.qml`、`src/frontend/greeter/CMakeLists.txt`、`src/common/mainconfig.kcfg`、`src/daemon/Display.cpp`、`src/helper/backend/PamBackend.cpp`、`src/common/Session.cpp`) - KDE bug 506025
- biopass PAM 文档
- biopass PAM 模块源码(退出码到 PAM 返回值的映射)
- Linux-PAM 调度实现
- `
man pam.conf`、`man pam_exec`