#!/usr/bin/env bash
set -euo pipefail
TOTAL=0
WARNINGS=0
WARNINGS_BLOCK=0
LUKS_DEV="${1:-/dev/nvme0n1p2}"
CMDLINE_FILE="/etc/kernel/cmdline"
CRYPTTAB_FILE="/etc/crypttab.initramfs"
MKINIT_CONF="/etc/mkinitcpio.conf"
note() { TOTAL=$((TOTAL+1)); printf "\e[32m[OK]\e[0m %s\n" "$*"; }
warn() { TOTAL=$((TOTAL+1)); WARNINGS=$((WARNINGS+1)); WARNINGS_BLOCK=$((WARNINGS_BLOCK+1)); printf "\e[33m[WARN]\e[0m %s\n" "$*"; }
warn_perm() { TOTAL=$((TOTAL+1)); WARNINGS=$((WARNINGS+1)); printf "\e[33m[WARN]\e[0m %s\n" "$*"; }
err() { printf "\e[31m[ERR]\e[0m %s\n" "$*"; }
on_exit() {
local status=$1
if [ $status -ne 0 ]; then
printf "\e[31m[EXIT]\e[0m 脚本异常退出,已执行 %d 项检查\n" "$TOTAL"
else
printf "\e[36m[SUMMARY]\e[0m 共执行 %d 项检查,警告 %d 项(阻断 %d 项)\n" "$TOTAL" "$WARNINGS" "$WARNINGS_BLOCK"
if [ "$WARNINGS_BLOCK" -eq 0 ]; then
printf "\e[32m[FINAL]\e[0m 无阻断警告,尝试 TPM 解锁验证...\n"
fi
fi
}
require() {
command -v "$1" >/dev/null 2>&1 || { err "缺少命令:$1"; exit 1; }
}
# 兼容不同 systemd-cryptenroll 版本的 TPM 解锁探测(不依赖 $?,避免 set -e 干扰)
try_unlock_tpm2() {
# 变体1:--unlock-tpm2 + --tpm2-device=auto
if systemd-cryptenroll "$LUKS_DEV" --unlock-tpm2 --tpm2-device=auto >/dev/null 2>&1; then
note "TPM 解锁验证(--unlock-tpm2 --tpm2-device=auto)成功"
return 0
fi
# 变体2:仅 --unlock-tpm2
if systemd-cryptenroll "$LUKS_DEV" --unlock-tpm2 >/dev/null 2>&1; then
note "TPM 解锁验证(--unlock-tpm2)成功"
return 0
fi
# 变体3:--unlock-tpm2 + --unlock-tpm2-device=auto(你的环境可用)
if systemd-cryptenroll "$LUKS_DEV" --unlock-tpm2 --unlock-tpm2-device=auto >/dev/null 2>&1; then
note "TPM 解锁验证(--unlock-tpm2 --unlock-tpm2-device=auto)成功"
return 0
fi
# 变体4:--tpm2-device=auto + --unlock-tpm2
if systemd-cryptenroll "$LUKS_DEV" --tpm2-device=auto --unlock-tpm2 >/dev/null 2>&1; then
note "TPM 解锁验证(--tpm2-device=auto --unlock-tpm2)成功"
return 0
fi
return 1
}
main() {
trap 'on_exit $?' EXIT
if [ "$EUID" -ne 0 ]; then exec sudo "$0" "$@"; fi
require cryptsetup
require lsblk
require awk
require grep
require blkid
if command -v bootctl >/dev/null 2>&1; then :; else warn "未安装/不可用 bootctl(systemd-boot 检查跳过)"; fi
if command -v tpm2_getrandom >/dev/null 2>&1; then :; else warn "未安装 tpm2-tools,无法进行 TPM 自检"; fi
if ! command -v systemd-cryptenroll >/dev/null 2>&1; then
err "缺少 systemd-cryptenroll"
exit 1
fi
# systemd 版本
if command -v systemd >/dev/null 2>&1; then
SDV=$(systemd --version | awk 'NR==1{print $2}')
if [ "${SDV:-0}" -ge 250 ]; then
note "systemd 版本 ${SDV}(满足 ≥250)"
else
warn "systemd 版本 ${SDV} 低于 250,TPM 集成可能不完整"
fi
fi
# TPM 设备存在性(优先 tpmrm0)
if [ -e /dev/tpmrm0 ]; then
note "检测到 /dev/tpmrm0(资源管理器设备)"
else
warn "未检测到 /dev/tpmrm0,TPM 并发与隔离较弱;尝试使用 /dev/tpm0"
fi
if [ -e /dev/tpm0 ]; then
note "检测到 /dev/tpm0"
else
err "未检测到 TPM 设备 /dev/tpm0"; exit 1
fi
if [ ! -b "$LUKS_DEV" ]; then
LUKS_DEV="$(blkid -t TYPE=crypto_LUKS -o device | head -n1 || true)"
if [ -z "$LUKS_DEV" ]; then err "未找到 LUKS 分区,请指定设备"; exit 1; fi
note "自动检测 LUKS 设备:$LUKS_DEV"
fi
LUKS_UUID=$(cryptsetup luksUUID "$LUKS_DEV" 2>/dev/null || true)
if [ -z "$LUKS_UUID" ]; then err "获取 LUKS UUID 失败,确认设备为 LUKS2 且可读(需要 root)"; exit 1; fi
note "LUKS 设备:$LUKS_DEV UUID=$LUKS_UUID"
# 确认 LUKS2
if cryptsetup luksDump "$LUKS_DEV" | grep -q "Version:.*2"; then
note "LUKS 版本为 LUKS2"
else
warn "LUKS 版本非 LUKS2,TPM 集成建议使用 LUKS2(可 convert)"
fi
# TPM keyslot & 密码 keyslot
DUMP=$(cryptsetup luksDump "$LUKS_DEV" 2>/dev/null || true)
if [ -z "$DUMP" ]; then
warn "无法读取 LUKS 头,请确认设备与权限"
fi
if grep -q "systemd-tpm2" <<<"$DUMP"; then
note "检测到 TPM2 keyslot(systemd-tpm2 token)"
else
warn "未检测到 TPM2 keyslot,请执行:systemd-cryptenroll ${LUKS_DEV} --tpm2-device=auto [--tpm2-pcrs=7+11]"
fi
PASS_SLOTS_COUNT=$(printf "%s\n" "$DUMP" | grep -E '^[[:space:]]*[0-9]+: luks' | wc -l || true)
if [ "${PASS_SLOTS_COUNT:-0}" -gt 0 ]; then
note "检测到密码 keyslot 数:$PASS_SLOTS_COUNT,可回退密码解锁"
else
warn "未检测到密码 keyslot,建议保留密码以便回退;添加:cryptsetup luksAddKey ${LUKS_DEV}"
fi
# TPM 可用性
if command -v tpm2_getrandom >/dev/null 2>&1; then
if tpm2_getrandom 8 >/dev/null 2>&1; then
note "TPM 设备可用(tpm2_getrandom 成功)"
else
warn "TPM 命令失败,重启后可能无法自动解锁,需密码回退"
fi
else
warn "未安装 tpm2-tools,无法测试 TPM"
fi
# cmdline 检查
if [ -f "$CMDLINE_FILE" ]; then
CMD=$(cat "$CMDLINE_FILE")
if grep -q "rd.luks.name=${LUKS_UUID}=cryptroot" <<<"$CMD"; then
note "cmdline 含 rd.luks.name=${LUKS_UUID}=cryptroot"
else
warn "cmdline 未包含 rd.luks.name=${LUKS_UUID}=cryptroot"
fi
if grep -q "root=/dev/mapper/cryptroot" <<<"$CMD"; then
note "cmdline root=/dev/mapper/cryptroot 匹配映射名"
else
warn "cmdline root= 未匹配 /dev/mapper/cryptroot"
fi
else
warn "未找到 $CMDLINE_FILE"
fi
# crypttab 检查(可选)
if [ -f "$CRYPTTAB_FILE" ]; then
if grep -q "$LUKS_UUID" "$CRYPTTAB_FILE"; then
note "crypttab.initramfs UUID 正确"
else
warn "crypttab.initramfs 未匹配 LUKS UUID=$LUKS_UUID"
fi
fi
# mkinitcpio HOOKS 检查
if [ -f "$MKINIT_CONF" ]; then
HOOKS_LINE=$(grep '^HOOKS=' "$MKINIT_CONF" || true)
if grep -q "sd-encrypt" <<<"$HOOKS_LINE" && grep -q "systemd" <<<"$HOOKS_LINE"; then
note "mkinitcpio HOOKS 包含 systemd 与 sd-encrypt"
else
warn "mkinitcpio HOOKS 缺少 systemd/sd-encrypt,请修正后 mkinitcpio -P"
fi
fi
# UKI/boot 状态(可选)
UKI_PATH="/boot/EFI/Linux/arch-linux.efi"
if [ -f "$UKI_PATH" ]; then
note "检测到 UKI:$UKI_PATH(时间:$(stat -c '%y' "$UKI_PATH"))"
else
warn "未检测到默认 UKI 路径 $UKI_PATH(如使用 GRUB 或自定义路径可忽略)"
fi
if command -v bootctl >/dev/null 2>&1; then
if bootctl status >/dev/null 2>&1; then
note "bootctl status 可用,确认引导项存在"
else
warn "bootctl status 失败,请检查 ESP 挂载与 systemd-boot"
fi
fi
# 权限检查(非阻断)
if [ -d /boot ]; then
PERM_BOOT=$(stat -c '%a' /boot)
if [ "$PERM_BOOT" != "700" ]; then
warn_perm "/boot 权限为 $PERM_BOOT(系统默认常为 755),可选加固:sudo chmod 700 /boot"
else
note "/boot 权限 700"
fi
fi
if [ -f /boot/loader/random-seed ]; then
PERM_SEED=$(stat -c '%a' /boot/loader/random-seed)
OWNER_SEED=$(stat -c '%U:%G' /boot/loader/random-seed)
if [ "$PERM_SEED" != "600" ] || [ "$OWNER_SEED" != "root:root" ]; then
warn_perm "random-seed 权限/属主为 $PERM_SEED $OWNER_SEED(系统默认可能 755 root:root),可选加固:sudo chown root:root /boot/loader/random-seed && sudo chmod 600 /boot/loader/random-seed"
else
note "random-seed 权限与属主符合要求"
fi
fi
# 最终:TPM 解锁自检(仅当无阻断警告时执行)
if [ "$WARNINGS_BLOCK" -eq 0 ]; then
if try_unlock_tpm2; then
note "最终 TPM 解锁验证成功:无需密码即可解锁 LUKS"
printf "\e[32m[SUCCESS]\e[0m 无阻断警告,TPM 将在启动时自动解锁 LUKS。\n"
else
err "最终 TPM 解锁验证失败。可能原因:PCR 变化、Secure Boot 状态变化、UKI 更新。尝试:使用密码解锁后,执行 systemd-cryptenroll ${LUKS_DEV} --wipe-slot=tpm2 && systemd-cryptenroll ${LUKS_DEV} --tpm2-device=auto --tpm2-pcrs=7+11"
exit 1
fi
else
warn "存在阻断警告,跳过最终 TPM 解锁验证。请先修复上述问题。"
fi
}
main "$@"
TPM解锁luks的Check脚本
“您的支持是我持续分享的动力”
微信
支付宝