Merge pull request #94 from boltgolt/dev

Version 2.4.0
This commit is contained in:
boltgolt 2018-11-09 21:44:01 +01:00 committed by GitHub
commit b4ecafe61c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 115 additions and 104 deletions

View file

@ -11,8 +11,8 @@ _Please describe the issue in as much detail as possible, including any errors a
---- ----
- [ ] I've searched for similar issues already, and my issue has not been reported yet. I've searched for similar issues already, and my issue has not been reported yet.
Linux distribution (if applicable): Linux distribution (if applicable):
Howdy version: Howdy version:

View file

@ -1,24 +1,26 @@
sudo: required sudo: required
language: python language: python
python: python: "3.6"
- "3.4"
- "3.6"
install:
# Install build tools and ack-grep for checks
- sudo apt install devscripts dh-make ack-grep -y
script: script:
# Build the binary (.deb) # Build the binary (.deb)
- debuild -i -us -uc -b - debuild -i -us -uc -b
# Install the binary, also fireing the debian scripts # Install the binary, running the debian scripts in the process
- sudo apt install ../*.deb -y - sudo apt install ../*.deb -y
# Confirm the cv2 module has been installed correctly
- sudo /usr/bin/env python3 -c "import cv2; print(cv2.__version__);"
# Confirm the face_recognition module has been installed correctly
- sudo /usr/bin/env python3 -c "import face_recognition; print(face_recognition.__version__);"
# Check if the username passthough works correctly with sudo # Check if the username passthough works correctly with sudo
- 'howdy | ack-grep --passthru --color "current active user: travis"' - 'howdy | ack-grep --passthru --color "current active user: travis"'
- 'sudo howdy | ack-grep --passthru --color "current active user: travis"' - 'sudo howdy | ack-grep --passthru --color "current active user: travis"'
# Remove howdy from the installation # Remove howdy from the installation
- sudo apt purge howdy -y - sudo apt purge howdy -y
notifications: notifications:
email: email:
on_success: never on_success: never
@ -27,3 +29,8 @@ notifications:
addons: addons:
apt: apt:
update: true update: true
packages:
- dh-make
- ack-grep
- devscripts
- fakeroot

10
debian/changelog vendored
View file

@ -1,3 +1,13 @@
howdy (2.4.0) xenial; urgency=medium
* Cameras are now selected by path instead of by video device number (thanks @Rhiyo!)
* Added fallbacks to $EDITOR for the config command (thanks @yassineim!)
* Fixed missing cv2 module after installation (thanks @bendandersen and many others!)
* Fixed file permissions crashing Howdy in some cases (thanks @GJDitchfield!)
* Fixed howdy using python3 from local virtual environment (thanks @EdwardJB!)
-- boltgolt <boltgolt@gmail.com> Fri, 09 Nov 2018 20:59:45 +0100
howdy (2.3.1) xenial; urgency=high howdy (2.3.1) xenial; urgency=high
* Fixed issue where `frame_width` and `frame_height` would be completely ignored (thanks @janecz-n!) * Fixed issue where `frame_width` and `frame_height` would be completely ignored (thanks @janecz-n!)

4
debian/control vendored
View file

@ -2,14 +2,14 @@ Source: howdy
Section: misc Section: misc
Priority: optional Priority: optional
Standards-Version: 3.9.7 Standards-Version: 3.9.7
Build-Depends: python, dh-python, devscripts, dh-make, debhelper Build-Depends: python, dh-python, devscripts, dh-make, debhelper, fakeroot
Maintainer: boltgolt <boltgolt@gmail.com> Maintainer: boltgolt <boltgolt@gmail.com>
Vcs-Git: https://github.com/boltgolt/howdy Vcs-Git: https://github.com/boltgolt/howdy
Package: howdy Package: howdy
Homepage: https://github.com/boltgolt/howdy Homepage: https://github.com/boltgolt/howdy
Architecture: all Architecture: all
Depends: ${misc:Depends}, git, python3, python3-pip, python3-dev, python3-setuptools, build-essential, libpam-python, fswebcam, libopencv-dev, python-opencv, cmake Depends: ${misc:Depends}, git, python3, python3-pip, python3-dev, python3-setuptools, libpam-python, fswebcam, libopencv-dev, python-opencv, cmake, streamer
Description: Howdy: Windows Hello style authentication for Linux. Description: Howdy: Windows Hello style authentication for Linux.
Use your built-in IR emitters and camera in combination with face recognition Use your built-in IR emitters and camera in combination with face recognition
to prove who you are. to prove who you are.

7
debian/howdy.lintian-overrides vendored Normal file
View file

@ -0,0 +1,7 @@
# W: Don't require ugly linebreaks in last 5 chars
howdy: debian-changelog-line-too-long
# E: Allows the name Howdy to show up in Ubuntu updater
howdy: description-starts-with-package-name
# E: Allows python for installation scripts
howdy: unknown-control-interpreter

54
debian/postinst vendored
View file

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/python3
# Installation script to install howdy # Installation script to install howdy
# Executed after primary apt install # Executed after primary apt install
@ -53,6 +53,11 @@ if not os.path.exists("/tmp/howdy_picked_device"):
# Go through every setting in the old config and apply it to the new file # Go through every setting in the old config and apply it to the new file
for section in oldConf.sections(): for section in oldConf.sections():
for (key, value) in oldConf.items(section): for (key, value) in oldConf.items(section):
# If config is still using the old device_id parameter, convert it to a path
if key == "device_id":
key = "device_path"
value = "/dev/video" + value
try: try:
newConf.set(section, key, value) newConf.set(section, key, value)
# Add a new section where needed # Add a new section where needed
@ -98,7 +103,7 @@ print("Temporary dlib files removed")
log("Installing python dependencies") log("Installing python dependencies")
# Install face_recognition though pip # Install direct dependencies so pip does not freak out with the manual dlib install
handleStatus(subprocess.call(["pip3", "install", "--cache-dir", "/tmp/pip_howdy", "face_recognition_models==0.3.0", "Click>=6.0", "numpy", "Pillow"])) 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") log("Installing face_recognition")
@ -106,19 +111,26 @@ log("Installing face_recognition")
# Install face_recognition though pip # Install face_recognition though pip
handleStatus(subprocess.call(["pip3", "install", "--cache-dir", "/tmp/pip_howdy", "--no-deps", "face_recognition==1.2.2"])) handleStatus(subprocess.call(["pip3", "install", "--cache-dir", "/tmp/pip_howdy", "--no-deps", "face_recognition==1.2.2"]))
try:
import cv2
except Exception as e:
log("Reinstalling opencv2")
handleStatus(subprocess.call(["pip3", "install", "opencv-python"]))
log("Configuring howdy") log("Configuring howdy")
# Manually change the camera id to the one picked # Manually change the camera id to the one picked
for line in fileinput.input(["/lib/security/howdy/config.ini"], inplace = 1): for line in fileinput.input(["/lib/security/howdy/config.ini"], inplace = 1):
print(line.replace("device_id = 1", "device_id = " + picked), end="") print(line.replace("device_path = none", "device_path = " + picked), end="")
print("Camera ID saved")
# Secure the howdy folder # Secure the howdy folder
handleStatus(subprocess.call(["chmod 600 -R /lib/security/howdy/"], shell=True)) handleStatus(subprocess.call(["chmod 744 -R /lib/security/howdy/"], shell=True))
# Allow anyone to execute the python CLI # Allow anyone to execute the python CLI
handleStatus(subprocess.call(["chmod 755 /lib/security/howdy"], shell=True)) 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 755 /lib/security/howdy/cli.py"], shell=True))
handleStatus(subprocess.call(["chmod 744 -R /lib/security/howdy/cli"], shell=True)) handleStatus(subprocess.call(["chmod 755 -R /lib/security/howdy/cli"], shell=True))
print("Permissions set") print("Permissions set")
# Make the CLI executable as howdy # Make the CLI executable as howdy
@ -198,33 +210,5 @@ common_auth = open("/etc/pam.d/common-auth", "w")
common_auth.write("".join(outlines)) common_auth.write("".join(outlines))
common_auth.close() common_auth.close()
# From here onwards the installation is complete # Sign off
# 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("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))

76
debian/preinst vendored
View file

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/python3
# Used to check cameras before commiting to install # Used to check cameras before commiting to install
# Executed before primary apt install of files # Executed before primary apt install of files
@ -35,7 +35,7 @@ if "install" not in sys.argv:
sys.exit(0) sys.exit(0)
# The default picked video device id # The default picked video device id
picked = -1 picked = "none"
print(col(1) + "Starting IR camera check...\n" + col(0)) print(col(1) + "Starting IR camera check...\n" + col(0))
@ -45,64 +45,62 @@ if "HOWDY_NO_PROMPT" in os.environ:
# Write the default device to disk and exit # Write the default device to disk and exit
with open("/tmp/howdy_picked_device", "w") as out_file: with open("/tmp/howdy_picked_device", "w") as out_file:
out_file.write("0") out_file.write("none")
sys.exit(0) sys.exit(0)
# Get all devices # Get all devices
devices = os.listdir("/dev") devices = os.listdir("/dev/v4l/by-path")
# Loop though all devices # Loop though all devices
for dev in devices: for dev in devices:
# Only use the video devices time.sleep(.5)
if (dev[:5] == "video"):
time.sleep(.5)
# The full path to the device is the default name # The full path to the device is the default name
device_name = "/dev/" + dev device_name = "/dev/v4l/by-path/" + dev
# Get the udevadm details to try to get a better name # Get the udevadm details to try to get a better name
udevadm = subprocess.check_output(["udevadm info -r --query=all -n " + device_name], shell=True).decode("utf-8") udevadm = subprocess.check_output(["udevadm info -r --query=all -n " + device_name], shell=True).decode("utf-8")
# Loop though udevadm to search for a better name # Loop though udevadm to search for a better name
for line in udevadm.split("\n"): for line in udevadm.split("\n"):
# Match it and encase it in quotes # Match it and encase it in quotes
re_name = re.search('product.*=(.*)$', line, re.IGNORECASE) re_name = re.search('product.*=(.*)$', line, re.IGNORECASE)
if re_name: if re_name:
device_name = '"' + re_name.group(1) + '"' device_name = '"' + re_name.group(1) + '"'
# Show what device we're using # Show what device we're using
print("Trying " + device_name) print("Trying " + device_name)
# Let fswebcam keep the camera open in the background # Let fswebcam keep the camera open in the background
sub = subprocess.Popen(["fswebcam -S 9999999999 -d /dev/" + dev + " /dev/null 2>/dev/null"], shell=True, preexec_fn=os.setsid) sub = subprocess.Popen(["streamer -t 1:0:0 -c /dev/v4l/by-path/" + dev + " -b 16 -f rgb24 -o /dev/null 1>/dev/null 2>/dev/null"], shell=True, preexec_fn=os.setsid)
try: try:
# Ask the user if this is the right one # Ask the user if this is the right one
print(col(2) + "One of your cameras should now be on." + col(0)) print(col(2) + "One of your cameras should now be on." + col(0))
ans = input("Did your IR emitters turn on? [y/N]: ") ans = input("Did your IR emitters turn on? [y/N]: ")
except KeyboardInterrupt: except KeyboardInterrupt:
# Kill fswebcam if the user aborts # Kill fswebcam if the user aborts
os.killpg(os.getpgid(sub.pid), signal.SIGTERM)
raise
# The user has answered, kill fswebcam
os.killpg(os.getpgid(sub.pid), signal.SIGTERM) os.killpg(os.getpgid(sub.pid), signal.SIGTERM)
raise
# Set this camera as picked if the answer was yes, go to the next one if no # The user has answered, kill fswebcam
if ans.lower().strip() == "y" or ans.lower().strip() == "yes": os.killpg(os.getpgid(sub.pid), signal.SIGTERM)
picked = dev[5:]
break # Set this camera as picked if the answer was yes, go to the next one if no
else: if ans.lower().strip() == "y" or ans.lower().strip() == "yes":
print("Interpreting as a " + col(3) + "\"NO\"\n" + col(0)) picked = dev
break
else:
print("Interpreting as a " + col(3) + "\"NO\"\n" + col(0))
# Abort if no camera was picked # Abort if no camera was picked
if picked == -1: if picked == "none":
print(col(3) + "No suitable IR camera found, aborting install." + col(0)) print(col(3) + "No suitable IR camera found, aborting install." + col(0))
sys.exit(23) sys.exit(23)
# Write the result to disk so postinst can have a look at it # Write the result to disk so postinst can have a look at it
with open("/tmp/howdy_picked_device", "w") as out_file: with open("/tmp/howdy_picked_device", "w") as out_file:
out_file.write(str(picked)) out_file.write("/dev/v4l/by-path/" + picked)
# Add a line break # Add a line break
print("") print("")

2
debian/prerm vendored
View file

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/python3
# Executed on deinstallation # Executed on deinstallation
# Completely remove howdy from the system # Completely remove howdy from the system

3
debian/rules vendored
View file

@ -6,6 +6,3 @@ include /usr/share/dpkg/default.mk
%: %:
dh $@ dh $@
binary:
dh binary

View file

@ -79,7 +79,7 @@ insert_model = {
} }
# Open the camera # Open the camera
video_capture = cv2.VideoCapture(int(config.get("video", "device_id"))) video_capture = cv2.VideoCapture(config.get("video", "device_path"))
# Force MJPEG decoding if true # Force MJPEG decoding if true
if config.get("video", "force_mjpeg") == "true": if config.get("video", "force_mjpeg") == "true":

View file

@ -1,12 +1,20 @@
# Open the config file in gedit # Open the config file in an editor
# Import required modules # Import required modules
import os import os
import time
import subprocess import subprocess
# Let the user know what we're doing # Let the user know what we're doing
print("Opening config.ini in the default editor") print("Opening config.ini in the default editor")
# Open gedit as a subprocess and fork it # Default to the nano editor
subprocess.call(["/etc/alternatives/editor", os.path.dirname(os.path.realpath(__file__)) + "/../config.ini"]) editor = "/bin/nano"
# Use the user preferred editor if available
if os.path.isfile("/etc/alternatives/editor"):
editor = "/etc/alternatives/editor"
elif "EDITOR" in os.environ:
editor = os.environ["EDITOR"]
# Open the editor as a subprocess and fork it
subprocess.call([editor, os.path.dirname(os.path.realpath(__file__)) + "/../config.ini"])

View file

@ -18,7 +18,7 @@ config = configparser.ConfigParser()
config.read(path + "/../config.ini") config.read(path + "/../config.ini")
# Start capturing from the configured webcam # Start capturing from the configured webcam
video_capture = cv2.VideoCapture(int(config.get("video", "device_id"))) video_capture = cv2.VideoCapture(config.get("video", "device_path"))
# Force MJPEG decoding if true # Force MJPEG decoding if true
if config.get("video", "force_mjpeg") == "true": if config.get("video", "force_mjpeg") == "true":

View file

@ -58,7 +58,7 @@ for model in models:
timings.append(time.time()) timings.append(time.time())
# Start video capture on the IR camera # Start video capture on the IR camera
video_capture = cv2.VideoCapture(int(config.get("video", "device_id"))) video_capture = cv2.VideoCapture(config.get("video", "device_path"))
# Force MJPEG decoding if true # Force MJPEG decoding if true
if config.get("video", "force_mjpeg") == "true": if config.get("video", "force_mjpeg") == "true":

View file

@ -29,9 +29,9 @@ certainty = 3.5
# The number of seconds to search before timing out # The number of seconds to search before timing out
timout = 4 timout = 4
# The /dev/videoX id to capture frames from # The path of the device to capture frames from
# Should be set automatically by the installer # Should be set automatically by the installer
device_id = 1 device_path = none
# Scale down the video feed to this maximum height # Scale down the video feed to this maximum height
# Speeds up face recognition but can make it less precise # Speeds up face recognition but can make it less precise

View file

@ -13,7 +13,7 @@ config = ConfigParser.ConfigParser()
config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini") config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini")
def doAuth(pamh): def doAuth(pamh):
"""Start authentication in a seperate process""" """Starts authentication in a seperate process"""
# Abort is Howdy is disabled # Abort is Howdy is disabled
if config.get("core", "disabled") == "true": if config.get("core", "disabled") == "true":
@ -25,7 +25,7 @@ def doAuth(pamh):
sys.exit(0) sys.exit(0)
# Run compare as python3 subprocess to circumvent python version and import issues # Run compare as python3 subprocess to circumvent python version and import issues
status = subprocess.call(["python3", os.path.dirname(os.path.abspath(__file__)) + "/compare.py", pamh.get_user()]) status = subprocess.call(["/usr/bin/python3", os.path.dirname(os.path.abspath(__file__)) + "/compare.py", pamh.get_user()])
# Status 10 means we couldn't find any face models # Status 10 means we couldn't find any face models
if status == 10: if status == 10:
@ -62,9 +62,9 @@ def pam_sm_open_session(pamh, flags, args):
return doAuth(pamh) return doAuth(pamh)
def pam_sm_close_session(pamh, flags, argv): def pam_sm_close_session(pamh, flags, argv):
"""We don't need to clean anyting up at the end of a session, so return true""" """We don't need to clean anyting up at the end of a session, so returns true"""
return pamh.PAM_SUCCESS return pamh.PAM_SUCCESS
def pam_sm_setcred(pamh, flags, argv): def pam_sm_setcred(pamh, flags, argv):
"""We don't need set any credentials, so return true""" """We don't need set any credentials, so returns true"""
return pamh.PAM_SUCCESS return pamh.PAM_SUCCESS