Created
June 7, 2025 23:57
-
-
Save Terry-BrooksJr/6f4a78feccb99d942988cfd9ec30fc9b to your computer and use it in GitHub Desktop.
Automated Script to Build, Install, and Configure Howdy as a PAM solution on Ubuntu 24.04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Howdy installation and configuration script | |
set -Eeuo pipefail | |
# Configuration constants | |
readonly HOWDY_REPO="https://github.com/boltgolt/howdy.git" | |
readonly HOWDY_BRANCH="beta" | |
readonly HOWDY_DIR="/opt/howdy" | |
readonly VENV_DIR="$HOWDY_DIR/.venv" | |
readonly PYTHON_BIN="$VENV_DIR/bin/python3" | |
readonly HOWDY_CONFIG="/usr/local/etc/howdy/config.ini" | |
readonly PAM_CONFIG="/usr/local/share/pam-configs/howdy" | |
readonly PAM_LINK="/usr/share/pam-configs/howdy" | |
readonly IR_EMITTER_DIR="/opt/linux-enable-ir-emitter" | |
# Required system packages | |
readonly REQUIRED_PACKAGES=( | |
git python3 python3-pip python3-setuptools python3-wheel | |
python3-opencv python3-dev python3-venv cmake make | |
build-essential meson ninja-build libpam0g-dev | |
libinih-dev libevdev-dev libopencv-dev v4l-utils qv4l2 | |
pkg-config | |
) | |
# Python packages for virtual environment | |
readonly PYTHON_PACKAGES=(pip dlib meson elevate keyboard) | |
# Global variables | |
DEVICE_PATH="" | |
# Logging functions | |
log_info() { | |
echo -e "\n==> [INFO] $*" | |
} | |
log_error() { | |
echo "[ERROR] $*" >&2 | |
} | |
log_warning() { | |
echo "[WARNING] $*" >&2 | |
} | |
# Error handling | |
cleanup_on_error() { | |
local exit_code=$? | |
log_error "Script failed at line $LINENO with exit code $exit_code" | |
log_error "Please check the logs above for more details." | |
exit $exit_code | |
} | |
trap cleanup_on_error ERR | |
trap 'echo "Script terminated prematurely." >&2; exit 1' SIGINT SIGTERM | |
# Help function | |
print_help() { | |
cat <<EOF | |
Howdy Installer Script | |
This script automates the installation and configuration of Howdy facial recognition on Linux. | |
Usage: | |
sudo ./install_howdy.sh [OPTIONS] | |
Options: | |
-h, --help Show this help message and exit. | |
Main Features: | |
• Installs required system packages | |
• Sets up Python virtual environment | |
• Clones Howdy (beta branch) | |
• Configures IR emitter (linux-enable-ir-emitter) | |
• Builds and installs Howdy | |
• Detects IR camera or prompts for device | |
• Updates PAM configuration for authentication | |
Requirements: | |
• Must be run as root (use sudo) | |
• Debian/Ubuntu-based system | |
• Must have linux-enable-ir-emitter installed please see https://github.com/EmixamPP/linux-enable-ir-emitter | |
• IR camera device | |
Post-installation: | |
• Run 'sudo howdy add' to enroll your face | |
• Test with 'sudo howdy test' | |
EOF | |
} | |
# Validation functions | |
check_root() { | |
if [[ $EUID -ne 0 ]]; then | |
log_error "This script must be run as root (use sudo)" | |
exit 1 | |
fi | |
} | |
confirm_ir_installed(){ | |
read -rp "Have You Installed linux-enable-ir-emitter (yes/no)?" precheck | |
if [[ $precheck !=* "yes" || $precheck !=* "y" ]]; then | |
echo "You must have linux-enable-ir-emitter installed please see https://github.com/EmixamPP/linux-enable-ir-emitter, then re-run script" | |
exit 1 | |
fi | |
} | |
validate_device_path() { | |
local device_path="$1" | |
if [[ ! -e "$device_path" ]]; then | |
log_error "Device path '$device_path' does not exist" | |
return 1 | |
fi | |
if [[ ! -c "$device_path" ]]; then | |
log_error "'$device_path' is not a character device" | |
return 1 | |
fi | |
return 0 | |
} | |
# Installation functions | |
install_system_packages() { | |
log_info "Updating package lists and installing required packages..." | |
export DEBIAN_FRONTEND=noninteractive | |
apt-get update -qq | |
apt-get install -y "${REQUIRED_PACKAGES[@]}" | |
} | |
setup_howdy_repository() { | |
log_info "Setting up Howdy repository..." | |
if [[ -d "$HOWDY_DIR" ]]; then | |
log_info "Howdy directory exists, removing and re-cloning for clean state..." | |
rm -rf "$HOWDY_DIR" | |
fi | |
git clone -b "$HOWDY_BRANCH" --quiet "$HOWDY_REPO" "$HOWDY_DIR" | |
cd "$HOWDY_DIR" | |
sudo chown -R terry-brooks:howdy . | |
} | |
setup_python_environment() { | |
log_info "Setting up Python virtual environment..." | |
# Create virtual environment if it doesn't exist | |
if [[ ! -d "$VENV_DIR" ]]; then | |
python3 -m venv "$VENV_DIR" --system-site-packages | |
log_info "Created virtual environment at: $VENV_DIR" | |
else | |
log_info "Virtual environment already exists at: $VENV_DIR" | |
fi | |
# Verify virtual environment structure | |
if [[ ! -f "$PYTHON_BIN" ]]; then | |
log_error "Python binary not found at: $PYTHON_BIN" | |
log_error "Virtual environment may not have been created properly" | |
exit 1 | |
fi | |
log_info "Installing Python packages in virtual environment..." | |
"$VENV_DIR/bin/pip" install --upgrade --quiet "${PYTHON_PACKAGES[@]}" | |
# Verify critical packages are installed | |
log_info "Verifying Python packages..." | |
"$PYTHON_BIN" -c "import dlib, cv2; print('Critical packages verified')" || { | |
log_error "Failed to import required Python packages" | |
log_error "Try running: $VENV_DIR/bin/pip list" | |
exit 1 | |
} | |
} | |
patch_meson_options() { | |
log_info "Configuring meson build options..." | |
local meson_options="$HOWDY_DIR/meson.options" | |
# Backup original file | |
cp "$meson_options" "$meson_options.backup" | |
# Update python_path option value only | |
if grep -q "option('python_path'" "$meson_options"; then | |
# Use sed to replace only the value part of the python_path option line | |
sed -i "/option('python_path'/s|value: '[^']*'|value: '$PYTHON_BIN'|" "$meson_options" | |
log_info "Updated Python path to: $PYTHON_BIN" | |
# Verify the change was made | |
local updated_line | |
updated_line=$(grep "option('python_path'" "$meson_options") | |
log_info "Python path option: $updated_line" | |
else | |
log_error "python_path option not found in meson.options" | |
log_error "Please check the meson.options file format" | |
exit 1 | |
fi | |
} | |
build_and_install_howdy() { | |
log_info "Building and installing Howdy..." | |
cd "$HOWDY_DIR" | |
# Activate virtual environment for Meson build | |
# shellcheck disable=SC1091 | |
source "$VENV_DIR/bin/activate" | |
# Verify virtual environment is active | |
if [[ -z "${VIRTUAL_ENV:-}" ]]; then | |
log_error "Virtual environment not activated!" | |
exit 1 | |
fi | |
log_info "Using Python: $(which python3)" | |
log_info "Virtual environment: $VIRTUAL_ENV" | |
# Clean previous build | |
[[ -d "build" ]] && rm -rf build | |
# Configure with explicit Python path and venv options | |
meson setup build \ | |
--python.install-env auto \ | |
-Dpython_path="$PYTHON_BIN" | |
meson compile -C build | |
meson install -C build | |
# Deactivate virtual environment | |
deactivate || true | |
} | |
detect_ir_camera() { | |
log_info "Detecting IR camera device..." | |
# List available video devices for reference | |
if command -v v4l2-ctl >/dev/null 2>&1; then | |
echo "Available video devices:" | |
be run as root (use sudo)v4l2-ctl --list-devices || true | |
echo | |
fi | |
# Try to auto-detect IR camera | |
local ir_device="" | |
if command -v v4l2-ctl >/dev/null 2>&1; then | |
# Look for devices with "IR" in the name | |
ir_device=$(v4l2-ctl --list-devices \ | |
| awk '/[Ii][Rr].*[Cc]amera/ {flag=1} flag && /^[[:space:]]*\/dev\/video/ {print $1; flag=0; exit}') | |
# Alternative: check device capabilities for IR | |
if [[ -z "$ir_device" ]]; then | |
for device in /dev/video*; do | |
if [[ -c "$device" ]] && v4l2-ctl --device "$device" --all 2>/dev/null | grep -qi "infrared\|IR"; then | |
ir_device="$device" | |
break | |
fi | |
done | |
fi | |
fi | |
# Prompt user for device path | |
if [[ -n "$ir_device" ]]; then | |
log_info "Infrared camera detected at: $ir_device" | |
read -rp "Use detected IR camera device [$ir_device] or enter different path: " user_input | |
DEVICE_PATH="${user_input:-$ir_device}" | |
else | |
log_warning "IR camera device not auto-detected" | |
while true; do | |
read -rp "Enter the IR camera device path (e.g., /dev/video2): " user_input | |
if [[ -n "$user_input" ]] && validate_device_path "$user_input"; then | |
DEVICE_PATH="$user_input" | |
break | |
else | |
log_warning "Invalid device path. Please try again." | |
fi | |
done | |
fi | |
log_info "Using IR camera device: $DEVICE_PATH" | |
} | |
configure_ir_emitter() { | |
log_info "Configuring IR emitter for device: $DEVICE_PATH" | |
# Check if linux-enable-ir-emitter is available | |
if ! command -v linux-enable-ir-emitter >/dev/null 2>&1; then | |
log_warning "linux-enable-ir-emitter not found, skipping IR emitter configuration" | |
log_warning "You may need to install and configure it manually" | |
return 0 | |
fi | |
# Run IR emitter configuration (may be interactive) | |
echo "Running IR emitter configuration. Follow any prompts below:" | |
# Temporarily disable error trapping for this function as linux-enable-ir-emitter returns a non-zero exit code if the Camera is already enabled | |
set +e | |
local output | |
output=$(echo "yes" | sudo linux-enable-ir-emitter --device "$DEVICE_PATH" configure 2>&1|| echo 'IR Emitter Enabled. Moving on') | |
local exit_status=$? | |
set -e | |
echo "$output" | |
return 0 | |
} | |
update_howdy_config() { | |
log_info "Updating Howdy configuration..." | |
if [[ -f "$HOWDY_CONFIG" ]]; then | |
# Update device path in config | |
sed -i -E "/^[[:space:]]*device_path\s*=/s|= *.*|= $DEVICE_PATH|" "$HOWDY_CONFIG" | |
log_info "Updated camera device path in Howdy config" | |
else | |
log_warning "Howdy config not found at $HOWDY_CONFIG" | |
log_warning "You may need to manually configure the device path" | |
fi | |
} | |
configure_pam() { | |
log_info "Configuring PAM authentication..." | |
# Create PAM config symlink if needed | |
if [[ -f "$PAM_CONFIG" && ! -L "$PAM_LINK" ]]; then | |
ln -sf "$PAM_CONFIG" "$PAM_LINK" | |
log_info "Created PAM config symlink" | |
fi | |
# Enable PAM authentication | |
if command -v pam-auth-update >/dev/null 2>&1; then | |
pam-auth-update --enable howdy || { | |
log_warning "'pam-auth-update --enable howdy' failed" | |
log_warning "You may need to enable Howdy in PAM manually" | |
} | |
else | |
log_warning "pam-auth-update not found, PAM configuration may need manual setup" | |
fi | |
} | |
# Main execution function | |
main() { | |
log_info "Starting Howdy installation and configuration..." | |
# Pre-flight checks | |
check_root | |
confirm_ir_installed | |
# Installation steps | |
install_system_packages | |
setup_howdy_repository | |
setup_python_environment | |
patch_meson_options | |
build_and_install_howdy | |
# IR camera and emitter configuration | |
detect_ir_camera | |
configure_ir_emitter | |
update_howdy_config | |
# PAM configuration | |
configure_pam | |
# Success message | |
log_info "Installation and configuration complete!" | |
echo | |
echo "Next steps:" | |
echo " 1. Add your face model: sudo howdy add" | |
echo " 2. Test recognition: sudo howdy test" | |
echo " 3. Check configuration: sudo howdy config" | |
echo | |
echo "If you encounter issues:" | |
echo " - Check camera permissions and device path" | |
echo " - Verify IR emitter is working: systemctl status linux-enable-ir-emitter" | |
echo " - Review Howdy logs: journalctl -u howdy" | |
} | |
# Handle command line arguments | |
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then | |
print_help | |
exit 0 | |
fi | |
# Execute main function | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment