Skip to content

Instantly share code, notes, and snippets.

@solidus1983
Forked from Terry-BrooksJr/get-howdy.sh
Created July 25, 2025 20:19
Show Gist options
  • Save solidus1983/f81c12a7ee93d97d65f95bf52594c390 to your computer and use it in GitHub Desktop.
Save solidus1983/f81c12a7ee93d97d65f95bf52594c390 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
#!/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