mirror of
https://github.com/boltgolt/howdy.git
synced 2024-09-19 09:51:19 +02:00
Completed CLI rework
This commit is contained in:
parent
0cdb4b78bd
commit
4f9e240869
11 changed files with 189 additions and 137 deletions
24
README.md
24
README.md
|
@ -14,7 +14,7 @@ sudo apt update
|
|||
sudo apt install howdy
|
||||
```
|
||||
|
||||
This will guide you through the installation. When that's done run `sudo howdy add $USER` and replace `$USER` with your username to add a face model.
|
||||
This will guide you through the installation. When that's done run `sudo howdy add` to add a face model.
|
||||
|
||||
If nothing went wrong we should be able to run sudo by just showing your face. Open a new terminal and run `sudo -i` to see it in action.
|
||||
|
||||
|
@ -22,22 +22,22 @@ If nothing went wrong we should be able to run sudo by just showing your face. O
|
|||
|
||||
### Command line
|
||||
|
||||
The installer adds a `howdy` command to manage face models for the current user. Use `howdy help` to list the available options.
|
||||
The installer adds a `howdy` command to manage face models for the current user. Use `howdy --help` to list the available options.
|
||||
|
||||
Usage:
|
||||
```
|
||||
howdy <command> [user] [argument]
|
||||
howdy [-U user] [-y] command [argument]
|
||||
```
|
||||
|
||||
| Command | Description | User required |
|
||||
|-----------|-----------------------------------------------|---------------|
|
||||
| `add` | Add a new face model for the given user | Yes |
|
||||
| `clear` | Remove all face models for the given user | Yes |
|
||||
| `config` | Open the config file in nano | No |
|
||||
| `disable` | Disable or enable howdy | No |
|
||||
| `list` | List all saved face models for the given user | Yes |
|
||||
| `remove` | Remove a specific model for the given user | Yes |
|
||||
| `test` | Test the camera and recognition methods | No |
|
||||
| Command | Description |
|
||||
|-----------|-----------------------------------------------|
|
||||
| `add` | Add a new face model for the given user |
|
||||
| `clear` | Remove all face models for the given user |
|
||||
| `config` | Open the config file in nano |
|
||||
| `disable` | Disable or enable howdy |
|
||||
| `list` | List all saved face models for the given user |
|
||||
| `remove` | Remove a specific model for the given user |
|
||||
| `test` | Test the camera and recognition methods |
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
|
|
115
src/cli.py
115
src/cli.py
|
@ -7,102 +7,91 @@ import os
|
|||
import subprocess
|
||||
import getpass
|
||||
import argparse
|
||||
import builtins
|
||||
|
||||
# Try to get the original username (not "root") from shell
|
||||
user = subprocess.check_output("echo $(logname 2>/dev/null || echo $SUDO_USER)", shell=True).decode("ascii").strip()
|
||||
|
||||
# If that fails, try to get the direct user
|
||||
if user == "root" or user == "":
|
||||
env_user = getpass.getuser().strip()
|
||||
|
||||
if env_user == "root" or env_user == "":
|
||||
# If even that fails, error out
|
||||
if env_user == "":
|
||||
print("Could not determine user, please use the --user flag")
|
||||
sys.exit(1)
|
||||
else:
|
||||
user = env_user
|
||||
|
||||
# Check if we have rootish rights
|
||||
if os.getenv("SUDO_USER") is None:
|
||||
print("Please run this command with sudo")
|
||||
sys.exit(1)
|
||||
|
||||
# Basic command setup
|
||||
parser = argparse.ArgumentParser(description="Command line interface for Howdy face authentication.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
add_help=False,
|
||||
prog="howdy",
|
||||
epilog="For support please visit\nhttps://github.com/Boltgolt/howdy")
|
||||
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
add_help=False,
|
||||
prog="howdy",
|
||||
epilog="For support please visit\nhttps://github.com/Boltgolt/howdy")
|
||||
|
||||
# Add an argument for the command
|
||||
parser.add_argument("command",
|
||||
help="The command option to execute, can be one of the following: add, clear, config, disable, list, remove or test.",
|
||||
metavar="command",
|
||||
choices=["add", "clear", "config", "disable", "list", "remove", "test"])
|
||||
help="The command option to execute, can be one of the following: add, clear, config, disable, list, remove or test.",
|
||||
metavar="command",
|
||||
choices=["add", "clear", "config", "disable", "list", "remove", "test"])
|
||||
|
||||
# Add an argument for the extra arguments of diable and remove
|
||||
parser.add_argument("argument",
|
||||
help="Either 0 or 1 for the disable command, or the model ID for the remove command.",
|
||||
nargs="?")
|
||||
help="Either 0 (enable) or 1 (disable) for the disable command, or the model ID for the remove command.",
|
||||
nargs="?")
|
||||
|
||||
# Add the user flag
|
||||
parser.add_argument("-U", "--user",
|
||||
default=user,
|
||||
default=user,
|
||||
help="Set the user account to use.")
|
||||
|
||||
# Add the -y flag
|
||||
parser.add_argument("-y",
|
||||
help="Skip all questions.",
|
||||
action="store_true")
|
||||
action="store_true")
|
||||
|
||||
# Overwrite the default help message so we can use a uppercase S
|
||||
parser.add_argument("-h", "--help",
|
||||
action="help",
|
||||
default=argparse.SUPPRESS,
|
||||
action="help",
|
||||
default=argparse.SUPPRESS,
|
||||
help="Show this help message and exit.")
|
||||
|
||||
# If we only have 1 argument we print the help text
|
||||
if len(sys.argv) < 2:
|
||||
print("current active user: " + user + "\n")
|
||||
parser.print_help()
|
||||
sys.exit(0)
|
||||
|
||||
# Parse all arguments above
|
||||
args = parser.parse_args()
|
||||
|
||||
print(args)
|
||||
sys.exit(0)
|
||||
# Save the args and user as builtins which can be accessed by the imports
|
||||
builtins.howdy_args = args
|
||||
builtins.howdy_user = args.user
|
||||
|
||||
# Check if if a command has been given and print help otherwise
|
||||
if (len(sys.argv) < 2):
|
||||
print("Howdy IR face recognition help")
|
||||
import cli.help
|
||||
sys.exit()
|
||||
# Beond this point the user can't change anymore, if we still have root as user we need to abort
|
||||
if args.user == "root":
|
||||
print("Can't run howdy commands as root, please run this command with the --user flag")
|
||||
sys.exit(1)
|
||||
|
||||
# The command given
|
||||
cmd = sys.argv[1]
|
||||
|
||||
# Call the right files for commands that don't need root
|
||||
if cmd == "help":
|
||||
print("Howdy IR face recognition")
|
||||
import cli.help
|
||||
elif cmd == "test":
|
||||
# Execute the right command
|
||||
if args.command == "add":
|
||||
import cli.add
|
||||
elif args.command == "clear":
|
||||
import cli.clear
|
||||
elif args.command == "config":
|
||||
import cli.config
|
||||
elif args.command == "disable":
|
||||
import cli.disable
|
||||
elif args.command == "list":
|
||||
import cli.list
|
||||
elif args.command == "remove":
|
||||
import cli.remove
|
||||
elif args.command == "test":
|
||||
import cli.test
|
||||
else:
|
||||
# Check if the minimum of 3 arugemnts has been met and print help otherwise
|
||||
if (len(sys.argv) < 3):
|
||||
print("Howdy IR face recognition help")
|
||||
import cli.help
|
||||
sys.exit()
|
||||
|
||||
# Requre sudo for comamnds that need root rights to read the model files
|
||||
if os.getenv("SUDO_USER") is None:
|
||||
print("Please run this command with sudo")
|
||||
sys.exit(1)
|
||||
|
||||
# Frome here on we require the second argument to be the username,
|
||||
# switching the command to the 3rd
|
||||
cmd = sys.argv[2]
|
||||
|
||||
if cmd == "list":
|
||||
import cli.list
|
||||
elif cmd == "add":
|
||||
import cli.add
|
||||
elif cmd == "remove":
|
||||
import cli.remove
|
||||
elif cmd == "clear":
|
||||
import cli.clear
|
||||
else:
|
||||
# If the comand is invalid, check if the user hasn't swapped the username and command
|
||||
if sys.argv[1] in ["list", "add", "remove", "clear"]:
|
||||
print("Usage: howdy <user> <command>")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('Unknown command "' + cmd + '"')
|
||||
import cli.help
|
||||
sys.exit(1)
|
||||
|
|
|
@ -8,6 +8,7 @@ import sys
|
|||
import json
|
||||
import cv2
|
||||
import configparser
|
||||
import builtins
|
||||
|
||||
# Try to import face_recognition and give a nice error if we can't
|
||||
# Add should be the first point where import issues show up
|
||||
|
@ -18,7 +19,7 @@ except ImportError as err:
|
|||
|
||||
print("\nCan't import the face_recognition module, check the output of")
|
||||
print("pip3 show face_recognition")
|
||||
sys.exit()
|
||||
sys.exit(1)
|
||||
|
||||
# Get the absolute path to the current file
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
@ -27,8 +28,7 @@ path = os.path.dirname(os.path.abspath(__file__))
|
|||
config = configparser.ConfigParser()
|
||||
config.read(path + "/../config.ini")
|
||||
|
||||
# The current user
|
||||
user = sys.argv[1]
|
||||
user = builtins.howdy_user
|
||||
# The permanent file to store the encoded model in
|
||||
enc_file = path + "/../models/" + user + ".dat"
|
||||
# Known encodings
|
||||
|
@ -46,11 +46,11 @@ except FileNotFoundError:
|
|||
encodings = []
|
||||
|
||||
# Print a warning if too many encodings are being added
|
||||
if len(encodings) > 2:
|
||||
if len(encodings) > 3:
|
||||
print("WARNING: Every additional model slows down the face recognition engine")
|
||||
print("Press ctrl+C to cancel")
|
||||
print("Press ctrl+C to cancel\n")
|
||||
|
||||
print("Adding face model for the user account " + user)
|
||||
print("Adding face model for the user " + user)
|
||||
|
||||
# Set the default label
|
||||
label = "Initial model"
|
||||
|
@ -59,12 +59,16 @@ label = "Initial model"
|
|||
if len(encodings) > 0:
|
||||
label = "Model #" + str(len(encodings) + 1)
|
||||
|
||||
# Ask the user for a custom label
|
||||
label_in = input("Enter a label for this new model [" + label + "]: ")
|
||||
# Keep de default name if we can't ask questions
|
||||
if builtins.howdy_args.y:
|
||||
print("Using default label \"" + label + "\" because of -y flag")
|
||||
else:
|
||||
# Ask the user for a custom label
|
||||
label_in = input("Enter a label for this new model [" + label + "]: ")
|
||||
|
||||
# Set the custom label (if any) and limit it to 24 characters
|
||||
if label_in != "":
|
||||
label = label_in[:24]
|
||||
# Set the custom label (if any) and limit it to 24 characters
|
||||
if label_in != "":
|
||||
label = label_in[:24]
|
||||
|
||||
# Prepare the metadata for insertion
|
||||
insert_model = {
|
||||
|
@ -106,12 +110,12 @@ while frames < 60:
|
|||
# If 0 faces are detected we can't continue
|
||||
if len(enc) == 0:
|
||||
print("No face detected, aborting")
|
||||
sys.exit()
|
||||
sys.exit(1)
|
||||
|
||||
# If more than 1 faces are detected we can't know wich one belongs to the user
|
||||
if len(enc) > 1:
|
||||
print("Multiple faces detected, aborting")
|
||||
sys.exit()
|
||||
sys.exit(1)
|
||||
|
||||
# Totally clean array that can be exported as JSON
|
||||
clean_enc = []
|
||||
|
@ -130,5 +134,6 @@ with open(enc_file, "w") as datafile:
|
|||
json.dump(encodings, datafile)
|
||||
|
||||
# Give let the user know how it went
|
||||
print("Scan complete")
|
||||
print("\nAdded a new model to " + user)
|
||||
print("""Scan complete
|
||||
|
||||
Added a new model to """ + user)
|
||||
|
|
|
@ -3,30 +3,33 @@
|
|||
# Import required modules
|
||||
import os
|
||||
import sys
|
||||
import builtins
|
||||
|
||||
# Get the full path to this file
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
# Get the passed user
|
||||
user = sys.argv[1]
|
||||
user = builtins.howdy_user
|
||||
|
||||
# Check if the models folder is there
|
||||
if not os.path.exists(path + "/../models"):
|
||||
print("No models created yet, can't clear them if they don't exist")
|
||||
sys.exit()
|
||||
sys.exit(1)
|
||||
|
||||
# Check if the user has a models file to delete
|
||||
if not os.path.isfile(path + "/../models/" + user + ".dat"):
|
||||
print(user + " has no models or they have been cleared already")
|
||||
sys.exit()
|
||||
sys.exit(1)
|
||||
|
||||
# Double check with the user
|
||||
print("This will clear all models for " + user)
|
||||
ans = input("Do you want to continue [y/N]: ")
|
||||
# Only ask the user if there's no -y flag
|
||||
if not builtins.howdy_args.y:
|
||||
# Double check with the user
|
||||
print("This will clear all models for " + user)
|
||||
ans = input("Do you want to continue [y/N]: ")
|
||||
|
||||
# Abort if they don't answer y or Y
|
||||
if (ans.lower() != "y"):
|
||||
print('\nInerpeting as a "NO"')
|
||||
sys.exit()
|
||||
# Abort if they don't answer y or Y
|
||||
if (ans.lower() != "y"):
|
||||
print('\nInerpeting as a "NO"')
|
||||
sys.exit(1)
|
||||
|
||||
# Delete otherwise
|
||||
os.remove(path + "/../models/" + user + ".dat")
|
||||
|
|
15
src/cli/config.py
Normal file
15
src/cli/config.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Open the config file in gedit
|
||||
|
||||
# Import required modules
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
# Let the user know what we're doing
|
||||
print("Opening condig.ini in gedit")
|
||||
|
||||
# Open gedit as a subprocess and fork it
|
||||
subprocess.Popen(["gedit", os.path.dirname(os.path.realpath(__file__)) + "/../config.ini"],
|
||||
cwd="/",
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
41
src/cli/disable.py
Normal file
41
src/cli/disable.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
# Set the disable flag
|
||||
|
||||
# Import required modules
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import builtins
|
||||
import fileinput
|
||||
import configparser
|
||||
|
||||
# Get the absolute filepath
|
||||
config_path = os.path.dirname(os.path.abspath(__file__)) + "/../config.ini"
|
||||
|
||||
# Read config from disk
|
||||
config = configparser.ConfigParser()
|
||||
config.read(config_path)
|
||||
|
||||
# Check if enough arguments have been passed
|
||||
if builtins.howdy_args.argument == None:
|
||||
print("Please add a 0 (enable) or a 1 (disable) as an argument")
|
||||
sys.exit(1)
|
||||
|
||||
# Translate the argument to the right string
|
||||
if builtins.howdy_args.argument == "1":
|
||||
out_value = "true"
|
||||
elif builtins.howdy_args.argument == "0":
|
||||
out_value = "false"
|
||||
else:
|
||||
# Of it's not a 0 or a 1, it's invalid
|
||||
print("Please only use a 0 (enable) or a 1 (disable) as an argument")
|
||||
sys.exit(1)
|
||||
|
||||
# Loop though the config file and only replace the line containing the disable config
|
||||
for line in fileinput.input([config_path], inplace=1):
|
||||
print(line.replace("disabled = " + config.get("core", "disabled"), "disabled = " + out_value), end="")
|
||||
|
||||
# Print what we just did
|
||||
if builtins.howdy_args.argument == "1":
|
||||
print("Howdy has been disabled")
|
||||
else:
|
||||
print("Howdy has been enabled")
|
|
@ -1,17 +0,0 @@
|
|||
# Prints a simple help page for the CLI
|
||||
|
||||
print("""
|
||||
Usage:
|
||||
howdy <user> <command> [argument]
|
||||
|
||||
Commands:
|
||||
help Show this help page
|
||||
list List all saved face models for the current user
|
||||
add Add a new face model for the current user
|
||||
remove [id] Remove a specific model
|
||||
clear Remove all face models for the current user
|
||||
test Test the camera and recognition methods
|
||||
|
||||
For support please visit
|
||||
https://github.com/Boltgolt/howdy\
|
||||
""")
|
|
@ -5,10 +5,11 @@ import sys
|
|||
import os
|
||||
import json
|
||||
import time
|
||||
import builtins
|
||||
|
||||
# Get the absolute path and the username
|
||||
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
|
||||
user = sys.argv[1]
|
||||
user = builtins.howdy_user
|
||||
|
||||
# Check if the models file has been created yet
|
||||
if not os.path.exists(path + "/models"):
|
||||
|
|
|
@ -4,22 +4,23 @@
|
|||
import sys
|
||||
import os
|
||||
import json
|
||||
import builtins
|
||||
|
||||
# Get the absolute path and the username
|
||||
path = os.path.dirname(os.path.realpath(__file__)) + "/.."
|
||||
user = sys.argv[1]
|
||||
user = builtins.howdy_user
|
||||
|
||||
# Check if enough arguments have been passed
|
||||
if len(sys.argv) == 3:
|
||||
print("Please add the ID of the model to remove as an argument")
|
||||
if builtins.howdy_args.argument == None:
|
||||
print("Please add the ID of the model you want to remove as an argument")
|
||||
print("You can find the IDs by running:")
|
||||
print("\n\thowdy " + user + " list\n")
|
||||
print("\n\thowdy list\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Check if the models file has been created yet
|
||||
if not os.path.exists(path + "/models"):
|
||||
print("Face models have not been initialized yet, please run:")
|
||||
print("\n\thowdy " + user + " add\n")
|
||||
print("\n\thowdy add\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Path to the models file
|
||||
|
@ -30,7 +31,7 @@ try:
|
|||
encodings = json.load(open(enc_file))
|
||||
except FileNotFoundError:
|
||||
print("No face model known for the user " + user + ", please run:")
|
||||
print("\n\thowdy " + user + " add\n")
|
||||
print("\n\thowdy add\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Tracks if a encoding with that id has been found
|
||||
|
@ -38,24 +39,28 @@ found = False
|
|||
|
||||
# Loop though all encodings and check if they match the argument
|
||||
for enc in encodings:
|
||||
if str(enc["id"]) == sys.argv[3]:
|
||||
# Double check with the user
|
||||
print('This will remove the model called "' + enc["label"] + '" for ' + user)
|
||||
ans = input("Do you want to continue [y/N]: ")
|
||||
if str(enc["id"]) == builtins.howdy_args.argument:
|
||||
# Only ask the user if there's no -y flag
|
||||
if not builtins.howdy_args.y:
|
||||
# Double check with the user
|
||||
print('This will remove the model called "' + enc["label"] + '" for ' + user)
|
||||
ans = input("Do you want to continue [y/N]: ")
|
||||
|
||||
# Abort if the answer isn't yes
|
||||
if (ans.lower() != "y"):
|
||||
print('\nInerpeting as a "NO"')
|
||||
sys.exit()
|
||||
# Abort if the answer isn't yes
|
||||
if (ans.lower() != "y"):
|
||||
print('\nInerpeting as a "NO"')
|
||||
sys.exit()
|
||||
|
||||
# Add a padding empty line
|
||||
print()
|
||||
|
||||
# Mark as found and print an enter
|
||||
found = True
|
||||
print()
|
||||
break
|
||||
|
||||
# Abort if no matching id was found
|
||||
if not found:
|
||||
print("No model with ID " + sys.argv[3] + " exists for " + user)
|
||||
print("No model with ID " + builtins.howdy_args.argument + " exists for " + user)
|
||||
sys.exit()
|
||||
|
||||
# Remove the entire file if this encoding is the only one
|
||||
|
@ -68,11 +73,11 @@ else:
|
|||
|
||||
# Loop though all encodin and only add thos that don't need to be removed
|
||||
for enc in encodings:
|
||||
if str(enc["id"]) != sys.argv[3]:
|
||||
if str(enc["id"]) != builtins.howdy_args.argument:
|
||||
new_encodings.append(enc)
|
||||
|
||||
# Save this new set to disk
|
||||
with open(enc_file, "w") as datafile:
|
||||
json.dump(new_encodings, datafile)
|
||||
|
||||
print("Removed model " + sys.argv[3])
|
||||
print("Removed model " + builtins.howdy_args.argument)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# Howdy config file
|
||||
|
||||
[core]
|
||||
# Do not print anything when a face verification succeeds
|
||||
no_confirmation = false
|
||||
|
@ -11,6 +13,10 @@ suppress_unknown = false
|
|||
# Expirimental, can behave incorrectly on some systems
|
||||
dismiss_lockscreen = false
|
||||
|
||||
# Disable howdy in the PAM
|
||||
# The howdy command will still function
|
||||
disabled = false
|
||||
|
||||
[video]
|
||||
# The certainty of the detected face belonging to the user of the account
|
||||
# On a scale from 1 to 10, values above 5 are not recommended
|
||||
|
|
|
@ -15,6 +15,10 @@ config.read(os.path.dirname(os.path.abspath(__file__)) + "/config.ini")
|
|||
def doAuth(pamh):
|
||||
"""Start authentication in a seperate process"""
|
||||
|
||||
# Abort is Howdy is disabled
|
||||
if config.get("core", "disabled") == "true":
|
||||
sys.exit(0)
|
||||
|
||||
# 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()])
|
||||
|
||||
|
|
Loading…
Reference in a new issue