Skip to content

Commit 40c290e

Browse files
authored
Merge pull request #118 from boltgolt/dev
Version 2.5.0
2 parents b4ecafe + e8d8692 commit 40c290e

34 files changed

+2832
-335
lines changed

.travis.yml

+7-9
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,16 @@ script:
88
# Install the binary, running the debian scripts in the process
99
- sudo apt install ../*.deb -y
1010

11-
# Confirm the cv2 module has been installed correctly
12-
- sudo /usr/bin/env python3 -c "import cv2; print(cv2.__version__);"
13-
# Confirm the face_recognition module has been installed correctly
14-
- sudo /usr/bin/env python3 -c "import face_recognition; print(face_recognition.__version__);"
15-
16-
# Check if the username passthough works correctly with sudo
17-
- 'howdy | ack-grep --passthru --color "current active user: travis"'
18-
- 'sudo howdy | ack-grep --passthru --color "current active user: travis"'
11+
# Go through function tests
12+
- ./tests/importing.sh
13+
- ./tests/passthrough.sh
14+
# Skip PAM integration tests for now because of broken pamtester
15+
# - ./tests/pam.sh
16+
- ./tests/compare.sh
1917

2018
# Remove howdy from the installation
2119
- sudo apt purge howdy -y
2220

23-
2421
notifications:
2522
email:
2623
on_success: never
@@ -34,3 +31,4 @@ addons:
3431
- ack-grep
3532
- devscripts
3633
- fakeroot
34+
- pamtester

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ Install the `howdy` package from the AUR. For AUR installation instructions, tak
2828

2929
You will need to do some additional configuration steps. Please read the [ArchWiki entry](https://wiki.archlinux.org/index.php/Howdy) for more information.
3030

31+
### Fedora
32+
The `howdy` package is now available in a [Fedora COPR repository](https://copr.fedorainfracloud.org/coprs/luya/howdy/) by simply execute the following command from a terminal:
33+
34+
```
35+
sudo dnf copr enable luya/howdy
36+
sudo dnf install howdy
37+
```
38+
3139
## Setup
3240

3341
After installation, you need to let Howdy learn your face. Run `sudo howdy add` to add a face model.

debian/changelog

+12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
howdy (2.5.0) xenial; urgency=medium
2+
3+
* Added FFmpeg and v4l2 recorders (thanks @timwelch!)
4+
* Added automatic PAM inclusion on installation
5+
* Added optional notice on detection attempt (thanks @mrkmg!)
6+
* Added support for grayscale frame encoding (thanks @dmig and @sapjunior!)
7+
* Massively improved recognition speed (thanks @dmig!)
8+
* Fixed typo in "timout" config value
9+
* Removed unneeded dependencies (thanks @dmig!)
10+
11+
-- boltgolt <[email protected]> Sun, 06 Jan 2019 14:37:41 +0100
12+
113
howdy (2.4.0) xenial; urgency=medium
214

315
* Cameras are now selected by path instead of by video device number (thanks @Rhiyo!)

debian/control

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ Vcs-Git: https://github.com/boltgolt/howdy
99
Package: howdy
1010
Homepage: https://github.com/boltgolt/howdy
1111
Architecture: all
12-
Depends: ${misc:Depends}, git, python3, python3-pip, python3-dev, python3-setuptools, libpam-python, fswebcam, libopencv-dev, python-opencv, cmake, streamer
12+
Depends: ${misc:Depends}, curl|wget, python3, python3-pip, python3-dev, python3-setuptools, libpam-python, fswebcam, libopencv-dev, cmake, streamer
13+
Recommends: libatlas-base-dev | libopenblas-dev | liblapack-dev
14+
Suggests: nvidia-cuda-dev (>= 7.5)
1315
Description: Howdy: Windows Hello style authentication for Linux.
1416
Use your built-in IR emitters and camera in combination with face recognition
1517
to prove who you are.

debian/install

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
src/. lib/security/howdy
2+
src/pam-config/. /usr/share/pam-configs
23
autocomplete/. usr/share/bash-completion/completions

debian/postinst

+124-105
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@
22
# Installation script to install howdy
33
# Executed after primary apt install
44

5+
56
def col(id):
67
"""Add color escape sequences"""
78
if id == 1: return "\033[32m"
89
if id == 2: return "\033[33m"
910
if id == 3: return "\033[31m"
1011
return "\033[0m"
1112

13+
1214
# Import required modules
15+
import fileinput
1316
import subprocess
14-
import time
1517
import sys
1618
import os
1719
import re
18-
import signal
19-
import fileinput
20-
import urllib.parse
20+
import tarfile
21+
from shutil import rmtree, which
2122

2223
# Don't run unless we need to configure the install
2324
# Will also happen on upgrade but we will catch that later on
@@ -29,13 +30,17 @@ def log(text):
2930
"""Print a nicely formatted line to stdout"""
3031
print("\n>>> " + col(1) + text + col(0) + "\n")
3132

33+
3234
def handleStatus(status):
3335
"""Abort if a command fails"""
3436
if (status != 0):
3537
print(col(3) + "Error while running last command" + col(0))
3638
sys.exit(1)
3739

3840

41+
# Create shorthand for subprocess creation
42+
sc = subprocess.call
43+
3944
# We're not in fresh configuration mode so don't continue the setup
4045
if not os.path.exists("/tmp/howdy_picked_device"):
4146
# Check if we have an older config we can restore
@@ -53,22 +58,34 @@ if not os.path.exists("/tmp/howdy_picked_device"):
5358
# Go through every setting in the old config and apply it to the new file
5459
for section in oldConf.sections():
5560
for (key, value) in oldConf.items(section):
61+
62+
# MIGRATION 2.3.1 -> 2.4.0
5663
# If config is still using the old device_id parameter, convert it to a path
5764
if key == "device_id":
5865
key = "device_path"
5966
value = "/dev/video" + value
6067

68+
# MIGRATION 2.4.0 -> 2.5.0
69+
# Finally correct typo in "timout" config value
70+
if key == "timout":
71+
key = "timeout"
72+
6173
try:
6274
newConf.set(section, key, value)
6375
# Add a new section where needed
64-
except configparser.NoSectionError as e:
76+
except configparser.NoSectionError:
6577
newConf.add_section(section)
6678
newConf.set(section, key, value)
6779

6880
# Write it all to file
6981
with open("/lib/security/howdy/config.ini", "w") as configfile:
7082
newConf.write(configfile)
7183

84+
# Install dlib data files if needed
85+
if not os.path.exists("/lib/security/howdy/dlib-data/shape_predictor_5_face_landmarks.dat"):
86+
print("Attempting installation of missing data files")
87+
handleStatus(subprocess.call(["./install.sh"], shell=True, cwd="/lib/security/howdy/dlib-data"))
88+
7289
sys.exit(0)
7390

7491
# Open the temporary file containing the device ID
@@ -78,137 +95,139 @@ picked = in_file.read()
7895
in_file.close()
7996

8097
# Remove the temporary file
81-
subprocess.call(["rm /tmp/howdy_picked_device"], shell=True)
98+
os.unlink("/tmp/howdy_picked_device")
8299

83100
log("Upgrading pip to the latest version")
84101

85102
# Update pip
86-
handleStatus(subprocess.call(["pip3 install --upgrade pip"], shell=True))
103+
handleStatus(sc(["pip3", "install", "--upgrade", "pip"]))
104+
105+
log("Downloading and unpacking data files")
106+
107+
# Run the bash script to download and unpack the .dat files needed
108+
handleStatus(subprocess.call(["./install.sh"], shell=True, cwd="/lib/security/howdy/dlib-data"))
109+
110+
log("Downloading dlib")
111+
112+
dlib_archive = "/tmp/v19.16.tar.gz"
113+
loader = which("wget")
114+
LOADER_CMD = None
115+
116+
# If wget is installed, use that as the downloader
117+
if loader:
118+
LOADER_CMD = [loader, "--tries", "5", "--output-document"]
119+
# Otherwise, fall back on curl
120+
else:
121+
loader = which("curl")
122+
LOADER_CMD = [loader, "--retry", "5", "--location", "--output"]
123+
124+
# Assemble and execute the download command
125+
cmd = LOADER_CMD + [dlib_archive, "https://github.com/davisking/dlib/archive/v19.16.tar.gz"]
126+
handleStatus(sc(cmd))
127+
128+
# The folder containing the dlib source
129+
DLIB_DIR = None
130+
# A regex of all files to ignore while unpacking the archive
131+
excludes = re.compile(
132+
"davisking-dlib-\w+/(dlib/(http_client|java|matlab|test/)|"
133+
"(docs|examples|python_examples)|"
134+
"tools/(archive|convert_dlib_nets_to_caffe|htmlify|imglab|python/test|visual_studio_natvis))"
135+
)
136+
137+
# Open the archive
138+
with tarfile.open(dlib_archive) as tf:
139+
for item in tf:
140+
# Set the destenation dir if unset
141+
if not DLIB_DIR:
142+
DLIB_DIR = "/tmp/" + item.name
143+
144+
# extract only files sufficient for building
145+
if not excludes.match(item.name):
146+
tf.extract(item, "/tmp")
147+
148+
# Delete the downloaded archive
149+
os.unlink(dlib_archive)
87150

88-
log("Cloning dlib")
151+
log("Building dlib")
89152

90-
# Clone the dlib git to /tmp, but only the last commit
91-
handleStatus(subprocess.call(["git", "clone", "--depth", "1", "https://github.com/davisking/dlib.git", "/tmp/dlib_clone"]))
153+
cmd = ["sudo", "python3", "setup.py", "install"]
154+
cuda_used = False
155+
flags = ""
156+
157+
# Get the CPU details
158+
with open("/proc/cpuinfo") as info:
159+
for line in info:
160+
if "flags" in line:
161+
flags = line
162+
break
163+
164+
# Use the most efficient instruction set the CPU supports
165+
if "avx" in flags:
166+
cmd += ["--yes", "USE_AVX_INSTRUCTIONS"]
167+
elif "sse4" in flags:
168+
cmd += ["--yes", "USE_SSE4_INSTRUCTIONS"]
169+
elif "sse3" in flags:
170+
cmd += ["--yes", "USE_SSE3_INSTRUCTIONS"]
171+
elif "sse2" in flags:
172+
cmd += ["--yes", "USE_SSE2_INSTRUCTIONS"]
173+
174+
# Compile and link dlib
175+
try:
176+
sp = subprocess.Popen(cmd, cwd=DLIB_DIR, stdout=subprocess.PIPE)
177+
except subprocess.CalledProcessError:
178+
print("Error while building dlib")
179+
raise
92180

93-
log("Building dlib")
181+
# Go through each line from stdout
182+
while sp.poll() is None:
183+
line = sp.stdout.readline().decode("utf-8")
94184

95-
# Start the build without GPU
96-
handleStatus(subprocess.call(["cd /tmp/dlib_clone/; python3 setup.py install --yes USE_AVX_INSTRUCTIONS --no DLIB_USE_CUDA"], shell=True))
185+
if "DLIB WILL USE CUDA" in line:
186+
cuda_used = True
187+
188+
print(line, end="")
97189

98190
log("Cleaning up dlib")
99191

100192
# Remove the no longer needed git clone
101-
handleStatus(subprocess.call(["rm", "-rf", "/tmp/dlib_clone"]))
102-
print("Temporary dlib files removed")
193+
del sp
194+
rmtree(DLIB_DIR)
103195

104-
log("Installing python dependencies")
105-
106-
# Install direct dependencies so pip does not freak out with the manual dlib install
107-
handleStatus(subprocess.call(["pip3", "install", "--cache-dir", "/tmp/pip_howdy", "face_recognition_models==0.3.0", "Click>=6.0", "numpy", "Pillow"]))
108-
109-
log("Installing face_recognition")
196+
print("Temporary dlib files removed")
110197

111-
# Install face_recognition though pip
112-
handleStatus(subprocess.call(["pip3", "install", "--cache-dir", "/tmp/pip_howdy", "--no-deps", "face_recognition==1.2.2"]))
198+
log("Installing OpenCV")
113199

114-
try:
115-
import cv2
116-
except Exception as e:
117-
log("Reinstalling opencv2")
118-
handleStatus(subprocess.call(["pip3", "install", "opencv-python"]))
200+
handleStatus(subprocess.call(["pip3", "install", "--no-cache-dir", "opencv-python"]))
119201

120202
log("Configuring howdy")
121203

122204
# Manually change the camera id to the one picked
123-
for line in fileinput.input(["/lib/security/howdy/config.ini"], inplace = 1):
124-
print(line.replace("device_path = none", "device_path = " + picked), end="")
205+
for line in fileinput.input(["/lib/security/howdy/config.ini"], inplace=1):
206+
line = line.replace("device_path = none", "device_path = " + picked)
207+
line = line.replace("use_cnn = false", "use_cnn = " + str(cuda_used).lower())
208+
209+
print(line, end="")
210+
125211
print("Camera ID saved")
126212

127213
# Secure the howdy folder
128-
handleStatus(subprocess.call(["chmod 744 -R /lib/security/howdy/"], shell=True))
214+
handleStatus(sc(["chmod 744 -R /lib/security/howdy/"], shell=True))
129215

130216
# Allow anyone to execute the python CLI
131-
handleStatus(subprocess.call(["chmod 755 /lib/security/howdy"], shell=True))
132-
handleStatus(subprocess.call(["chmod 755 /lib/security/howdy/cli.py"], shell=True))
133-
handleStatus(subprocess.call(["chmod 755 -R /lib/security/howdy/cli"], shell=True))
217+
os.chmod("/lib/security/howdy", 0o755)
218+
os.chmod("/lib/security/howdy/cli.py", 0o755)
219+
handleStatus(sc(["chmod 755 -R /lib/security/howdy/cli"], shell=True))
134220
print("Permissions set")
135221

136222
# Make the CLI executable as howdy
137-
handleStatus(subprocess.call(["ln -s /lib/security/howdy/cli.py /usr/local/bin/howdy"], shell=True))
138-
handleStatus(subprocess.call(["chmod +x /usr/local/bin/howdy"], shell=True))
223+
os.symlink("/lib/security/howdy/cli.py", "/usr/local/bin/howdy")
224+
os.chmod("/usr/local/bin/howdy", 0o755)
139225
print("Howdy command installed")
140226

141227
log("Adding howdy as PAM module")
142228

143-
# Will be filled with the actual output lines
144-
outlines = []
145-
# Will be fillled with lines that contain coloring
146-
printlines = []
147-
# Track if the new lines have been insterted yet
148-
inserted = False
149-
150-
# Open the PAM config file
151-
with open("/etc/pam.d/common-auth") as fp:
152-
# Read the first line
153-
line = fp.readline()
154-
155-
while line:
156-
# Add the line to the output directly, we're not deleting anything
157-
outlines.append(line)
158-
159-
# Print the comments in gray and don't insert into comments
160-
if line[:1] == "#":
161-
printlines.append("\033[37m" + line + "\033[0m")
162-
else:
163-
printlines.append(line)
164-
165-
# If it's not a comment and we haven't inserted yet
166-
if not inserted:
167-
# Set both the comment and the linking line
168-
line_comment = "# Howdy IR face recognition\n"
169-
line_link = "auth sufficient pam_python.so /lib/security/howdy/pam.py\n\n"
170-
171-
# Add them to the output without any markup
172-
outlines.append(line_comment)
173-
outlines.append(line_link)
174-
175-
# Make the print orange to make it clear what's being added
176-
printlines.append("\033[33m" + line_comment + "\033[0m")
177-
printlines.append("\033[33m" + line_link + "\033[0m")
178-
179-
# Mark as inserted
180-
inserted = True
181-
182-
# Go to the next line
183-
line = fp.readline()
184-
185-
# Print a file Header
186-
print("\033[33m" + ">>> START OF /etc/pam.d/common-auth" + "\033[0m")
187-
188-
# Loop though all printing lines and use the enters from the file
189-
for line in printlines:
190-
print(line, end="")
191-
192-
# Print a footer
193-
print("\033[33m" + ">>> END OF /etc/pam.d/common-auth" + "\033[0m" + "\n")
194-
195-
# Do not prompt for a yes if we're in no promt mode
196-
if "HOWDY_NO_PROMPT" not in os.environ:
197-
# Ask the user if this change is okay
198-
print("Lines will be insterted in /etc/pam.d/common-auth as shown above")
199-
ans = input("Apply this change? [y/N]: ")
200-
201-
# Abort the whole thing if it's not
202-
if ans.lower().strip() != "y" or ans.lower().strip() == "yes":
203-
print("Interpreting as a \"NO\", aborting")
204-
sys.exit(1)
205-
206-
print("Adding lines to PAM\n")
207-
208-
# Write to PAM
209-
common_auth = open("/etc/pam.d/common-auth", "w")
210-
common_auth.write("".join(outlines))
211-
common_auth.close()
229+
# Activate the pam-config file
230+
handleStatus(subprocess.call(["pam-auth-update --package"], shell=True))
212231

213232
# Sign off
214233
print("Installation complete.")

0 commit comments

Comments
 (0)