2
2
# Installation script to install howdy
3
3
# Executed after primary apt install
4
4
5
+
5
6
def col (id ):
6
7
"""Add color escape sequences"""
7
8
if id == 1 : return "\033 [32m"
8
9
if id == 2 : return "\033 [33m"
9
10
if id == 3 : return "\033 [31m"
10
11
return "\033 [0m"
11
12
13
+
12
14
# Import required modules
15
+ import fileinput
13
16
import subprocess
14
- import time
15
17
import sys
16
18
import os
17
19
import re
18
- import signal
19
- import fileinput
20
- import urllib .parse
20
+ import tarfile
21
+ from shutil import rmtree , which
21
22
22
23
# Don't run unless we need to configure the install
23
24
# Will also happen on upgrade but we will catch that later on
@@ -29,13 +30,17 @@ def log(text):
29
30
"""Print a nicely formatted line to stdout"""
30
31
print ("\n >>> " + col (1 ) + text + col (0 ) + "\n " )
31
32
33
+
32
34
def handleStatus (status ):
33
35
"""Abort if a command fails"""
34
36
if (status != 0 ):
35
37
print (col (3 ) + "Error while running last command" + col (0 ))
36
38
sys .exit (1 )
37
39
38
40
41
+ # Create shorthand for subprocess creation
42
+ sc = subprocess .call
43
+
39
44
# We're not in fresh configuration mode so don't continue the setup
40
45
if not os .path .exists ("/tmp/howdy_picked_device" ):
41
46
# Check if we have an older config we can restore
@@ -53,22 +58,34 @@ if not os.path.exists("/tmp/howdy_picked_device"):
53
58
# Go through every setting in the old config and apply it to the new file
54
59
for section in oldConf .sections ():
55
60
for (key , value ) in oldConf .items (section ):
61
+
62
+ # MIGRATION 2.3.1 -> 2.4.0
56
63
# If config is still using the old device_id parameter, convert it to a path
57
64
if key == "device_id" :
58
65
key = "device_path"
59
66
value = "/dev/video" + value
60
67
68
+ # MIGRATION 2.4.0 -> 2.5.0
69
+ # Finally correct typo in "timout" config value
70
+ if key == "timout" :
71
+ key = "timeout"
72
+
61
73
try :
62
74
newConf .set (section , key , value )
63
75
# Add a new section where needed
64
- except configparser .NoSectionError as e :
76
+ except configparser .NoSectionError :
65
77
newConf .add_section (section )
66
78
newConf .set (section , key , value )
67
79
68
80
# Write it all to file
69
81
with open ("/lib/security/howdy/config.ini" , "w" ) as configfile :
70
82
newConf .write (configfile )
71
83
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
+
72
89
sys .exit (0 )
73
90
74
91
# Open the temporary file containing the device ID
@@ -78,137 +95,139 @@ picked = in_file.read()
78
95
in_file .close ()
79
96
80
97
# Remove the temporary file
81
- subprocess . call ([ "rm /tmp/howdy_picked_device"], shell = True )
98
+ os . unlink ( " /tmp/howdy_picked_device" )
82
99
83
100
log ("Upgrading pip to the latest version" )
84
101
85
102
# 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 )
87
150
88
- log ("Cloning dlib" )
151
+ log ("Building dlib" )
89
152
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
92
180
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" )
94
184
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 = "" )
97
189
98
190
log ("Cleaning up dlib" )
99
191
100
192
# 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 )
103
195
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" )
110
197
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" )
113
199
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" ]))
119
201
120
202
log ("Configuring howdy" )
121
203
122
204
# 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
+
125
211
print ("Camera ID saved" )
126
212
127
213
# 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 ))
129
215
130
216
# 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 ))
134
220
print ("Permissions set" )
135
221
136
222
# 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 )
139
225
print ("Howdy command installed" )
140
226
141
227
log ("Adding howdy as PAM module" )
142
228
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 ))
212
231
213
232
# Sign off
214
233
print ("Installation complete." )
0 commit comments