22# Installation script to install howdy
33# Executed after primary apt install
44
5+
56def 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
1316import subprocess
14- import time
1517import sys
1618import os
1719import 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+
3234def 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
4045if 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()
7895in_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
83100log ("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
98190log ("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
120202log ("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+
125211print ("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 ))
134220print ("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 )
139225print ("Howdy command installed" )
140226
141227log ("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
214233print ("Installation complete." )
0 commit comments