#!/usr/bin/env python3 # Installation script to install howdy # Executed after primary apt install def col(id): """Add color escape sequences""" if id == 1: return "\033[32m" if id == 2: return "\033[33m" if id == 3: return "\033[31m" return "\033[0m" # Import required modules import subprocess import time import sys import os import re import signal import fileinput import urllib.parse # Don't run unless we need to configure the install # Will also happen on upgrade but we will catch that later on if "configure" not in sys.argv: sys.exit(0) def log(text): """Print a nicely formatted line to stdout""" print("\n>>> " + col(1) + text + col(0) + "\n") def handleStatus(status): """Abort if a command fails""" if (status != 0): print(col(3) + "Error while running last command" + col(0)) sys.exit(1) # We're not in fresh configuration mode so don't continue the setup if not os.path.exists("/tmp/howdy_picked_device"): # Check if we have an older config we can restore if len(sys.argv) > 2: if os.path.exists("/tmp/howdy_config_backup_v" + sys.argv[2] + ".ini"): # Get the config parser import configparser # Load th old and new config files oldConf = configparser.ConfigParser() oldConf.read("/tmp/howdy_config_backup_v" + sys.argv[2] + ".ini") newConf = configparser.ConfigParser() newConf.read("/lib/security/howdy/config.ini") # Go through every setting in the old config and apply it to the new file for section in oldConf.sections(): for (key, value) in oldConf.items(section): try: newConf.set(section, key, value) # Add a new section where needed except configparser.NoSectionError as e: newConf.add_section(section) newConf.set(section, key, value) # Write it all to file with open("/lib/security/howdy/config.ini", "w") as configfile: newConf.write(configfile) sys.exit(0) # Open the temporary file containing the device ID in_file = open("/tmp/howdy_picked_device", "r") # Load it in, it should be a string picked = in_file.read() in_file.close() # Remove the temporary file subprocess.call(["rm /tmp/howdy_picked_device"], shell=True) log("Upgrading pip to the latest version") # Update pip handleStatus(subprocess.call(["pip3 install --upgrade pip"], shell=True)) log("Cloning dlib") # Clone the dlib git to /tmp, but only the last commit handleStatus(subprocess.call(["git", "clone", "--depth", "1", "https://github.com/davisking/dlib.git", "/tmp/dlib_clone"])) log("Building dlib") # Start the build without GPU handleStatus(subprocess.call(["cd /tmp/dlib_clone/; python3 setup.py install --yes USE_AVX_INSTRUCTIONS --no DLIB_USE_CUDA"], shell=True)) log("Cleaning up dlib") # Remove the no longer needed git clone handleStatus(subprocess.call(["rm", "-rf", "/tmp/dlib_clone"])) print("Temporary dlib files removed") log("Installing python dependencies") # Install face_recognition though pip handleStatus(subprocess.call(["pip3", "install", "--cache-dir", "/tmp/pip_howdy", "face_recognition_models==0.3.0", "Click>=6.0", "numpy", "Pillow"])) log("Installing face_recognition") # Install face_recognition though pip handleStatus(subprocess.call(["pip3", "install", "--cache-dir", "/tmp/pip_howdy", "--no-deps", "face_recognition==1.2.2"])) log("Configuring howdy") # Manually change the camera id to the one picked for line in fileinput.input(["/lib/security/howdy/config.ini"], inplace = 1): print(line.replace("device_id = 1", "device_id = " + picked), end="") # Secure the howdy folder handleStatus(subprocess.call(["chmod 600 -R /lib/security/howdy/"], shell=True)) # Allow anyone to execute the python CLI handleStatus(subprocess.call(["chmod 755 /lib/security/howdy"], shell=True)) handleStatus(subprocess.call(["chmod 744 /lib/security/howdy/cli.py"], shell=True)) handleStatus(subprocess.call(["chmod 744 -R /lib/security/howdy/cli"], shell=True)) print("Permissions set") # Make the CLI executable as howdy handleStatus(subprocess.call(["ln -s /lib/security/howdy/cli.py /usr/local/bin/howdy"], shell=True)) handleStatus(subprocess.call(["chmod +x /usr/local/bin/howdy"], shell=True)) print("Howdy command installed") log("Adding howdy as PAM module") # Will be filled with the actual output lines outlines = [] # Will be fillled with lines that contain coloring printlines = [] # Track if the new lines have been insterted yet inserted = False # Open the PAM config file with open("/etc/pam.d/common-auth") as fp: # Read the first line line = fp.readline() while line: # Add the line to the output directly, we're not deleting anything outlines.append(line) # Print the comments in gray and don't insert into comments if line[:1] == "#": printlines.append("\033[37m" + line + "\033[0m") else: printlines.append(line) # If it's not a comment and we haven't inserted yet if not inserted: # Set both the comment and the linking line line_comment = "# Howdy IR face recognition\n" line_link = "auth sufficient pam_python.so /lib/security/howdy/pam.py\n\n" # Add them to the output without any markup outlines.append(line_comment) outlines.append(line_link) # Make the print orange to make it clear what's being added printlines.append("\033[33m" + line_comment + "\033[0m") printlines.append("\033[33m" + line_link + "\033[0m") # Mark as inserted inserted = True # Go to the next line line = fp.readline() # Print a file Header print("\033[33m" + ">>> START OF /etc/pam.d/common-auth" + "\033[0m") # Loop though all printing lines and use the enters from the file for line in printlines: print(line, end="") # Print a footer print("\033[33m" + ">>> END OF /etc/pam.d/common-auth" + "\033[0m" + "\n") # Do not prompt for a yes if we're in no promt mode if "HOWDY_NO_PROMPT" not in os.environ: # Ask the user if this change is okay print("Lines will be insterted in /etc/pam.d/common-auth as shown above") ans = input("Apply this change? [y/N]: ") # Abort the whole thing if it's not if ans.lower().strip() != "y" or ans.lower().strip() == "yes": print("Interpreting as a \"NO\", aborting") sys.exit(1) print("Adding lines to PAM\n") # Write to PAM common_auth = open("/etc/pam.d/common-auth", "w") common_auth.write("".join(outlines)) common_auth.close() # From here onwards the installation is complete # We want to gather more information about the types or IR camera's # used though, and the following lines are data gathering # No data is ever uploaded without permission if "HOWDY_NO_PROMPT" not in os.environ: # List all video devices diag_out = "Video devices [IR=" + picked + "]\n" diag_out += "```\n" diag_out += subprocess.check_output(['ls /dev/ | grep video'], shell=True).decode("utf-8") diag_out += "```\n" # Get some info from the USB kernel listings diag_out += "Lsusb output\n" diag_out += "```\n" diag_out += subprocess.check_output(['lsusb -vvvv | grep -i "Camera\|iFunction"'], shell=True).decode("utf-8") diag_out += "```\n" # Get camera information from video4linux diag_out += "Udevadm\n" diag_out += "```\n" diag_out += subprocess.check_output(['udevadm info -r --query=all -n /dev/video' + picked + ' | grep -i "ID_BUS\|ID_MODEL_ID\|ID_VENDOR_ID\|ID_V4L_PRODUCT\|ID_MODEL"'], shell=True).decode("utf-8") diag_out += "```" # Print it all as a clickable link to a new github issue print("https://github.com/boltgolt/howdy-reports/issues/new?title=Post-installation%20camera%20information&body=" + urllib.parse.quote_plus(diag_out) + "\n") # Let the user know what to do with the link print("Installation complete.") print(col(2) + "If you want to help the development, please use the link above to post some camera-related information to github!" + col(0))