Installing and testing mlapi on the same host as ZM and ZMES

The reason this is different than installing mlapi on its own host without ZM and ZMES is that ZMES uses the system user ‘www-data’ to run the ZMES system automatically. ZM has a logging system that ZMES interfaces with to correctly write logs. ZMES piggybacks on some of the systems that ZM set up. When mlapi is on its own host using a different user, it needs to be configured to rotate logs and be installed as a systemd service. The intended setup is to create a custom user to run mlapi and install the systemd service under that username. You then configure the ‘log_user’ and ‘log_group’ options in mlapiconfig for the user that runs mlapi so that the logs are written correctly. When mlapi is without ZMES it will log to Syslog and file, but Syslog at a minimum. I added INFO level logging for failed auth attempts when logging into mlapi for detection requests, this allows a fail2ban jail to be set up for mlapi. ZMES uses ZM AUTH so the login attempts will be in the ZM web_php.log

This is the easier of the 2 installs so it will be done first, I will then clone the LXC and spin up a brand new LXC with the clone, uninstall ZM and configure a custom user to run mlapi as. This way I don’t need to rebuild OpenCV, Dlib, etc.

MLAPI on the same host as ZMES

cd ~/git
git clone https://github.com/baudneo/mlapi.git
cd mlapi
# Install dependencies (libev is for bjoern to be built when doing requirements.txt)sudo apt install libev-dev libevdev2 libgeos-dev
sudo -H pip3 install -r requirements.txt
# Now we create a MLAPI user (What ZMES uses to request detections from mlapi) In this example the user will be named 'mlapitest' and the password will be 'test123'
# for help use: python3 mlapi_dbuser.py --help
python3 mlapi_dbuser.py -u mlapitest -p test123
User: testmlapi created
# If you get an error about ->
# FileNotFoundError: [Errno 2] No such file or directory: './db/db.json'
mkdir db
python3 mlapi_dbuser.py -u mlapitest -p test123

Since this system has ZM and ZMES installed the best place for the mlapi folder will be with zmeventnotification, so copy over the mlapi folder to where zmeventnotification is installed (/var/lib/zmeventnotification by default).

# still inside ~/git/mlapi, remove the 'models' directory, after we copy directory we will symbolically link the existing models folder into mlapi
rmdir ./models
sudo -u www-data cp -r ../mlapi /var/lib/zmeventnotification# Check that the mlapi folder is where it should be and has proper permissions for www-datalsd -all /var/lib/zmeventnotification/mlapi# Symbollically link the models from ZMES into the mlapi folder, mlapi folder should not have a physical folder named 'models'sudo -u www-data ln -s /var/lib/zmeventnotification/models /var/lib/zmeventnotification/mlapi/models# Check that the symbolic link works ->lsd -all /var/lib/zmeventnotification/mlapi# should show a linked folder for models ->
lrwxrwxrwx www-data www-data 35 B Wed Oct 20 17:23:31 2021 models ⇒ /var/lib/zmeventnotification/models

Configure and test MLAPI on the same host as ZMES

Before we set up mlapi as a systemd service and add the log rotation configuration file, we need to configure it to test. First, cd into the mlapi installation dir.

cd /var/lib/zmeventnotification/mlapi
# Get an encryption key, there is a default one to just test but use your own!
python3 get_encryption_key.pyThis is provided to generate a predefined key for encrypting credentials sent between zmeventnotification and mlapi.You can run this as many times as you want, it does not 'remember' anything.See default configuration for description and example. Encryption key -->SGqZkIaXqotqZb6tZzGhqL-1E5jrbX--kNMrnGDWRMU=
# Obviously your key will be different, anytime you want to 'roll' the key just run this file and then copy the new encryption key into mlapiconfig and objectconfig for the specified route.

Now open the secrets.ini file and change the MLAPI_SECRET_KEY to some random string, this is for signing/minting JWT auth tokens; which reminds me I need to make a system to manage separate mlapi AUTH token files now that mlapi is dynamic. ZMES could also use many different mlapi hosts and this will, at best, confuse ZMES and at worst, break it when ZMES makes detection requests to more than 1 mlapi instance.

sudo -u www-data secrets.ini
# enter a random string for the mlapi_secret_key secret, save and exit.
# Open mlapiconfig.ini
sudo -u www-data nano mlapiconfig.ini
# Uncomment the ;wsgi_server=bjoern line so that we use bjoern instead of Flask.wsgi_server = bjoern

Delete the encryption key for the mlapi_one entry in ‘zmes_keys’ and then add the new encryption key. MAKE SURE that the encryption key is enclosed in single or double quotes!

zmes_keys = {# use different keys for each route!# Format -> 'name of key/route/zmes host': '<key>',# notice how in the example objectconfig.ini in ml_routes there is an entry for mlapi_one and both keys are the same# NAME and its KEY must match in objectconfig.ini !'mlapi_one': 'SGqZkIaXqotqZb6tZzGhqL-1E5jrbX--kNMrnGDWRMU=',;                       'warehouse': 'run get_encryption_key.py for an encryption key and use the same key here and in objectconfig.ini ml_routes',;                       'main office': 'run get_encryption_key.py for an encryption key and use the same key here and in objectconfig.ini ml_routes',} # Always keep the closing brace indented

Go through the rest of mlapiconfig and change any options you want. AN IMPORTANT THING TO DO is to change all the PATH options to ABSOLUTE paths. For example, base_data_path=. by default, change that to /var/lib/zmeventnotification/mlapi . Go through the file and try and change most of the base path options to absolute paths.

I am not changing anything else for the test. Now open /etc/zm/secrets.ini to add the encryption key and configure the secrets for mlapi test ->

# Open /etc/zm/secrets.ini and configure the secretsML_USER = mlapitest
ML_PASSWORD = test123
# The ZM API user that mlapi will use to get the images and event info, I am using the same user that ZMES uses for the test. In production I reccomend to create an API user for each mlapi instance.ZM_ML_USER=testuser
ZM_ML_PASSWORD=test123
# Encryption key for mlapi_one routemlapi_one_key = SGqZkIaXqotqZb6tZzGhqL-1E5jrbX--kNMrnGDWRMU=# Save and exit

Next, open /etc/zm/objectconfig.ini to enable ml_routes by configuring ml_enable = yesand then change the ‘gateway’ option in the mlapi_one ml_route. It needs to be changed to localhost, localhost will work because ZMES and mlapi are on the same host.

{   'weight': 0,
'name': 'mlapi_one',
'gateway': 'http://localhost:5000/api/v1',
'user': '{[ML_USER]}',
'pass': '{[ML_PASSWORD]}',
'zm_user': '{[ZM_ML_USER]}',
'zm_pass': '{[ZM_ML_PASSWORD]}',
'zm_basic_user': '',
'zm_basic_pass': '',
'enc_key': '{[mlapi_one_key]}',
},

Notice that the name of the ml_route is mlapi_one in objectconfig.ini and the name of the encryption key is mlapi_one in mlapiconfig.ini. THIS IS IMPORTANT, if the names don’t match mlapi will not be able to grab the encryption key to decrypt the credentials and URL it needs to reply. Also, if the encryption keys are not the same, mlapi will not be able to properly decrypt the data!

MLAPI Shell Aliases

# Open your shell config file (.bashrc, .zshrc, etc.)# Add these mlapi alaises, I changed the paths to match where mlapi is installedalias mlapi.start='sudo -u www-data python3 /var/lib/zmeventnotification/mlapi/mlapi.py --config /var/lib/zmeventnotification/mlapi/mlapiconfig.ini &'alias mlapi.debug='sudo -u www-data python3 /var/lib/zmeventnotification/mlapi/mlapi.py --config /var/lib/zmeventnotification/mlapi/mlapiconfig.ini -d &'alias mlapi.baredebug='sudo -u www-data python3 /var/lib/zmeventnotification/mlapi/mlapi.py --config /var/lib/zmeventnotification/mlapi/mlapiconfig.ini --baredebug &'# use the -d or --debug flag to enable printing to the console, if you already are monitoring the logs using the cat/bat# commands above use the -bd --baredebug option to not print output to the console (or use this option when you see double output to the console)#alias mlapi.logs='sudo -u www-data tail -F /var/lib/zmeventnotification/mlapi/logs/zm_mlapi.log &'#alias mlapi.syslog="sudo tail -F /var/log/syslog &"#IF YOU HAVE DOWNLOADED 'bat - the cat replacement' uncomment these 2 lines instead of the 2 abovealias mlapi.logs='sudo -u www-data tail -F /var/lib/zmeventnotification/mlapi/logs/zm_mlapi.log | bat --paging never -l log --style changes,header,rule,snip &'alias mlapi.syslog="sudo tail -F /var/log/syslog | bat --paging never -l log --style changes,header,rule,snip --theme 'Solarized (dark)' &"#-- The mlapi.init aliases assume you have mlapi installed as a servicealias mlapi.init.start='sudo systemctl start mlapi.service &'alias mlapi.init.restart='sudo systemctl restart mlapi.service &'alias mlapi.init.stop='sudo systemctl stop mlapi.service &'alias mlapi.init.status='sudo systemctl status mlapi.service &'

The mlapi.init.x commands will not work until we install mlapi as a service, for now we will test by running mlapi.debug

mlapi.debug
# There should be a bunch of output that ends with ->
10/20/21 19:20:23.482493 zm_mlapi[3532] INF mlapi:676->[mlapi: using bjoern as WSGI server @ '0.0.0.0:5000']# Next we can use the same event as testing ZMES ->
es.debug.objdet 15
# Look at the output to confirm mlapi is getting the detection and replying, see if there are any errors, if there are there is a good chance they are simple configuration errors.

Train Faces

Relevant docs from the original author of ZMES -> Here

# You need to have a folder with some decent headshots for training the face model.
# Example, faces for a person named Jeff
sudo -u www-data mkdir /var/lib/zmeventnotification/mlapi/known_faces/jeff# Place all the photos of Jeff in the mlapi/known_faces/jeff/ directorycd /var/lib/zmeventnotification/mlapisudo -u www-data ./mlapi_face_train.py -c mlapiconfig.ini -d# Should print to console and end with something similar ->10/20/21 23:55:55.057296 zm_mlapi[14047] DBG1 face_train_dlib:160[mlapi:face-train: wrote encoding file: /var/lib/zmeventnotification/mlapi/known_faces/faces.dat]10/20/21 23:55:55.077544 zm_mlapi[14047] DBG1 face_train_dlib:162->[perf: Face Recognition training took: 12943.44 ms]# You can delete the faces.dat and retrain if you want to add more photos or different people.

MLAPI as a systemD service, Log rotation

Now to install mlapi as a systemd service and set up the log rotation for mlapi logs.

sudo nano /etc/logrotate.d/mlapi# Put this in the file, save and exit
/var/lib/zmeventnotification/mlapi/logs/zm_mlapi*.log {
missingoksu www-data www-datacopytruncatenotifemptysharedscriptsdelaycompresscompressdailyrotate 7maxage 7}# That takes care of the mlapi log rotation.------------------------------------------------------------
# Now to install as a service. www-data is the user that mlapi runs under.

nano /ver/lib/zmeventnotification/mlapi/mlapi.service
# Edit the file to conform to this install ->[Unit]
Description=Machine Learning API service
After=network.target
StartLimitIntervalSec=3

[Service]
Type=simple
Restart=always
RestartSec=5
# We need this to get logs correctly
Environment
=PYTHONUNBUFFERED=1

# change this
WorkingDirectory
=/var/lib/zmeventnotification/mlapi
# Change to your username
User
=www-data
#Change paths if needed
ExecStart
=/var/lib/zmeventnotification/mlapi/mlapi.py -c /var/lib/zmeventnotification/mlapi/mlapiconfig.ini
ExecStartPost=+/bin/sh -c 'umask 022; pgrep mlapi.py > /var/run/mlapi.pid'


# These may have no effect, keep them anyways!
StandardOutput
=file:/home/mlapi/git/mlapi/logs/zm_mlapi.log
StandardError=file:/home/mlapi/git/mlapi/logs/zm_mlapi.log

[Install]
WantedBy=multi-user.target
# Save and exit, INSTALLcd /var/lib/zmeventnotification/mlapisudo cp /var/lib/zmeventnotification/mlapi/mlapi.service /etc/systemd/systemsudo systemctl daemon-reload
sudo systemctl enable mlapi.service
# If mlapi is not running already, use the init alias, they are short commands for the systemctl commands->
mlapi.init.start # If you did not add the handy aliases from TERM_helpers.txt ->sudo systemctl start mlapi.service
# mlapi should be auto started on every reboot!
# Start breaking things and let me know!

This article will be split up so it isn’t so long and will be updated as things change. This is hopefully up to snuff to help absolutely new users get this up and running. I will be writing more articles to show how to configure things in depth and explain the new features and enhancements.

Here is a screenshot of why I like using the aliases that use the bat program for output. This is also why I use 2 different themes for mlapi and ZMES. When you are on the same host you can really differentiate the logs. Purple/White is mlapi and grey/green is ZMES.

Log porn

Troubleshooting

Check the permissions on the folders and files! Especially if you are getting weird errors or no errors. Even the permissions on the /etc/zm/conf.d directory and its contents have an effect on ZMES.

sudo chown -R www-data:www-data /etc/zm
sudo chown root:www-data /etc/zm/zm.conf
sudo chown -R root:www-data /etc/zm/conf.d
sudo chown -R www-data:www-data /var/lib/zmeventnotification
sudo chmod -R g-w+r /etc/zm/conf.d
sudo chmod g-w+r /etc/zm/zm.conf
If you have not added your user to be able to use sudo without using a password, you should. It can be setup for specific commands and specific users:groups!

As I run into problems I will update this section.

Other sections of this article.
* Part 1 — Install ZoneMinder 1.36.x
* Part 2 — NVIDIA drivers, Cuda 10.2, cuDNN 8.2.1, TPU Libs
* Part 3 — Build and Install OpenCV 4.5.4
* Part 4 — Configure objectconfig.ini and test ZMES local
* Part 5 — Build DLib (face recognition) and ALPR
* Part 6 — Install and test mlapi on the same host
* Part 7 — Install and test mlapi on a remote host (No ZMES)

--

--