Dotfiles/desktopenvs/hyprlua/scripts/enroll-biometrics.sh

252 lines
9.5 KiB
Bash
Executable File

#!/bin/bash
# enroll-biometrics.sh — TUI for face biometric setup.
#
# Two subsystems:
# 1. Presence detection — configure/test the webcam used by presence-detect.sh
# 2. Howdy face auth — enroll/manage/test face models for PAM authentication
BACKTITLE="Biometric Enrollment"
PRESENCE_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/presence-detect.conf"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PYTHON_DETECT="$SCRIPT_DIR/python/presence_detect.py"
# ── Dialog theme (Cyberqueer) ─────────────────────────────────────────────────
TMP_D=$(mktemp -d)
trap 'rm -rf "$TMP_D"' EXIT INT TERM
export DIALOGRC="$TMP_D/dialogrc"
cat > "$DIALOGRC" << 'RCEOF'
use_shadow = ON
use_colors = ON
screen_color = (BLACK,BLACK,ON)
shadow_color = (BLACK,BLACK,ON)
title_color = (MAGENTA,BLACK,ON)
border_color = (MAGENTA,BLACK,ON)
button_active_color = (BLACK,MAGENTA,ON)
button_inactive_color = (WHITE,BLACK,OFF)
button_key_active_color = (BLACK,CYAN,ON)
button_key_inactive_color = (CYAN,BLACK,ON)
button_label_active_color = (BLACK,MAGENTA,ON)
button_label_inactive_color = (WHITE,BLACK,OFF)
inputbox_color = (WHITE,BLACK,OFF)
inputbox_border_color = (MAGENTA,BLACK,ON)
menubox_color = (WHITE,BLACK,OFF)
menubox_border_color = (MAGENTA,BLACK,ON)
item_color = (WHITE,BLACK,OFF)
item_selected_color = (BLACK,MAGENTA,ON)
tag_color = (CYAN,BLACK,ON)
tag_selected_color = (BLACK,CYAN,ON)
check_color = (WHITE,BLACK,OFF)
check_selected_color = (BLACK,MAGENTA,ON)
uarrow_color = (MAGENTA,BLACK,ON)
darrow_color = (MAGENTA,BLACK,ON)
RCEOF
# ── Helpers ───────────────────────────────────────────────────────────────────
die() { clear; printf "\nError: %s\n\n" "$1" >&2; exit 1; }
msg() {
dialog --backtitle "$BACKTITLE" --title " $1 " --msgbox "\n$2\n" "$3" "$4"
}
require_dialog() {
command -v dialog &>/dev/null && return
sudo pacman -S --noconfirm dialog || die "dialog not found"
}
# ── Camera helpers ─────────────────────────────────────────────────────────────
list_cameras() {
for dev in /dev/video*; do
[[ -c "$dev" ]] || continue
local id="${dev#/dev/video}"
local name
name=$(v4l2-ctl --device="$dev" --info 2>/dev/null \
| awk -F': ' '/Card type/{print $2}' | head -1)
[[ -z "$name" ]] && name="Camera $id"
printf '%s\n%s\n' "$id" "$name"
done
}
get_camera_id() {
if [[ -n "$PRESENCE_DETECT_CAMERA" ]]; then
echo "$PRESENCE_DETECT_CAMERA"
elif [[ -f "$PRESENCE_CFG" ]]; then
grep -oP 'CAMERA=\K[0-9]+' "$PRESENCE_CFG" 2>/dev/null || echo 0
else
echo 0
fi
}
set_camera_id() {
mkdir -p "$(dirname "$PRESENCE_CFG")"
printf 'CAMERA=%s\n' "$1" > "$PRESENCE_CFG"
}
# ── Presence detection ────────────────────────────────────────────────────────
presence_configure_camera() {
local -a cam_items
mapfile -t cam_items < <(list_cameras)
if [[ ${#cam_items[@]} -eq 0 ]]; then
msg "No Cameras Found" \
"No video devices found at /dev/video*.\n\nMake sure your webcam is connected." \
10 55
return
fi
local current; current=$(get_camera_id)
local choice
choice=$(dialog --backtitle "$BACKTITLE" \
--title " Select Camera " \
--menu "\nCurrent: /dev/video${current}\n\nSelect camera to use for presence detection:" \
16 62 6 \
"${cam_items[@]}" \
3>&1 1>&2 2>&3) || return
set_camera_id "$choice"
msg "Camera Set" \
"Presence detection will use /dev/video${choice}.\n\nRestart presence-detect.sh for the change to take effect." \
9 62
}
presence_test_camera() {
local cam; cam=$(get_camera_id)
clear
printf "\n\033[1;35m Testing presence detection on /dev/video%s...\033[0m\n" "$cam"
printf "\033[35m ─────────────────────────────────────────\033[0m\n\n"
printf " Look at the camera and stay still.\n\n"
python3 "$PYTHON_DETECT" "$cam" 2>/dev/null
local rc=$?
case $rc in
0) msg "Test Result" "Face detected!\n\nPresence detection is working correctly." 8 52 ;;
1) msg "Test Result" \
"No face detected.\n\nMake sure you are in front of the camera\nand there is adequate lighting." \
10 56 ;;
2) msg "Camera Error" \
"Could not open /dev/video${cam}.\n\nTry configuring a different camera first." \
9 56 ;;
*) msg "Error" "Unexpected exit code ($rc) from detection script." 7 52 ;;
esac
}
# ── Howdy helpers ─────────────────────────────────────────────────────────────
howdy_installed() { command -v howdy &>/dev/null; }
howdy_require() {
howdy_installed && return 0
dialog --backtitle "$BACKTITLE" --title " Howdy Not Found " \
--yesno "\nhowdy is not installed.\n\nInstall it now via yay?" 8 48 || return 1
clear
printf "\nInstalling howdy...\n\n"
if command -v yay &>/dev/null; then
yay -S --noconfirm --needed howdy
else
sudo pacman -S --noconfirm --needed howdy
fi
howdy_installed || { msg "Install Failed" "howdy installation failed.\nInstall it manually: yay -S howdy" 8 52; return 1; }
}
# ── Howdy operations ──────────────────────────────────────────────────────────
howdy_add() {
howdy_require || return
local name
name=$(dialog --backtitle "$BACKTITLE" \
--title " Add Face Model " \
--inputbox "\nEnter a label for this face model:" 8 52 "$USER" \
3>&1 1>&2 2>&3) || return
[[ -z "$name" ]] && return
clear
printf "\n\033[1;35m Enrolling face model '%s'...\033[0m\n" "$name"
printf "\033[35m ─────────────────────────────────────────\033[0m\n\n"
printf " Look at the camera and hold still.\n\n"
sudo howdy add -n "$name"
local rc=$?
if [[ $rc -eq 0 ]]; then
msg "Enrolled" \
"Face model '$name' added.\n\nYou may need to relogin for PAM changes to take effect." \
9 58
else
msg "Failed" "Enrollment failed (code $rc).\n\nCheck camera and lighting." 8 52
fi
}
howdy_list() {
howdy_require || return
local out; out=$(sudo howdy list 2>&1)
msg "Enrolled Face Models" "$out" 20 64
}
howdy_remove() {
howdy_require || return
local models; models=$(sudo howdy list 2>&1)
if [[ -z "$models" || "$models" == *"no models"* ]]; then
msg "No Models" "No face models are currently enrolled." 7 44
return
fi
local id
id=$(dialog --backtitle "$BACKTITLE" \
--title " Remove Face Model " \
--inputbox "\nCurrent models:\n\n${models}\n\nEnter model ID to remove:" 20 64 \
3>&1 1>&2 2>&3) || return
[[ -z "$id" || ! "$id" =~ ^[0-9]+$ ]] && {
msg "Invalid ID" "Please enter a numeric model ID." 6 36; return
}
dialog --backtitle "$BACKTITLE" --title " Confirm " \
--yesno "\nRemove face model ID $id?" 7 40 || return
sudo howdy remove -I "$id"
msg "Done" "Face model $id removed." 6 38
}
howdy_test() {
howdy_require || return
clear
printf "\n\033[1;35m Testing howdy authentication...\033[0m\n"
printf "\033[35m ─────────────────────────────────────────\033[0m\n\n"
printf " Look at the camera.\n\n"
sudo howdy test
local rc=$?
if [[ $rc -eq 0 ]]; then
msg "Result" "Authentication successful." 6 38
else
msg "Result" "Authentication failed (code $rc).\n\nCheck enrolled models and camera setup." 9 52
fi
}
# ── Main menu ─────────────────────────────────────────────────────────────────
main_menu() {
local choice
choice=$(dialog --backtitle "$BACKTITLE" \
--title " Biometric Enrollment " \
--menu "\nSelect an option:" 20 66 8 \
"1" "Presence detection — configure camera" \
"2" "Presence detection — test detection" \
"3" "Howdy face auth — add face model" \
"4" "Howdy face auth — list enrolled models" \
"5" "Howdy face auth — remove face model" \
"6" "Howdy face auth — test authentication" \
3>&1 1>&2 2>&3) || { clear; exit 0; }
case "$choice" in
1) presence_configure_camera ;;
2) presence_test_camera ;;
3) howdy_add ;;
4) howdy_list ;;
5) howdy_remove ;;
6) howdy_test ;;
esac
main_menu
}
require_dialog
main_menu